<!DOCTYPE html>
<html><head>
<title>用Unity3D实现【智能巡逻兵】</title>




<meta charset="utf-8">
<meta name="X-UA-Compatible" content="IE=edge">
<meta name="google-site-verification" content="">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
<meta content="telephone=no" name="format-detection">
<meta name="description" content="">
<meta name="renderer" content="webkit">
<meta name="theme-color" content="#ffffff">



<meta property="og:title" content="用Unity3D实现【智能巡逻兵】" />
<meta property="og:description" content="用Unity3D实现【智能巡逻兵】  作业要求  游戏设计要求：智能巡逻兵  创建一个地图和若干巡逻兵(使用动画)； 每个巡逻兵走一个3~5个边的凸多边型，位置数据是相对地址。即每次确定下一个目标位置，用自己当前位置为原点计算； 巡逻兵碰撞到障碍物，则会自动选下一个点为目标； 巡逻兵在设定范围内感知到玩家，会自动追击玩家； 失去玩家目标后，继续巡逻； 计分：玩家每次甩掉一个巡逻兵计一分，与巡逻兵碰撞游戏结束；   程序设计要求：  必须使用订阅与发布模式传消息  Subject（：OnLostGoal）、Publisher、Subscriber   工厂模式生产巡逻兵    项目文档 - 智能巡逻兵 项目仓库：https://gitee.com/WondrousWisdomcard/unity3d-homework
一、游戏规则 开始游戏后，玩家进入有16个房间的密室，除了玩家开始所在的餐厅之外，每个房间有若干宝石和幽灵，玩家需要集齐所有水晶来打开驱除幽灵，但要小心，不要被幽灵抓到。
游戏开始自动计时，没有时间限制，每摆脱一个幽灵可以获得一分，16个房间中随机散布着10个不同的宝石，除了邪恶的钟表房间有5个幽灵和餐厅没有幽灵之外，其余的房间只有3个幽灵，注意，每个房间东西南北的四个缝隙可能会被柜子或者壁橱堵住。
二、游戏效果 2.1 游戏效果         2.2 视频演示 视频地址：https://www.bilibili.com/video/BV1QQ4y1v7Ag?spm_id_from=333.999.0.0 （不知怎么紫的发亮）
三、项目配置过程   到 AssetStore 下载：
  游戏素材：3D Beginner: Tutorial Resources
  宝石素材：Simple Gems Ultimate Animated Customizable Pack
            直接复制以下代码到项目中：代码" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://wondrouswisdomcard.gitee.io/posts/unity3d4/" />
<meta property="article:published_time" content="2021-11-28T00:00:00+00:00" />
<meta property="article:modified_time" content="2021-11-28T00:00:00+00:00" /><meta property="og:site_name" content="My Blog" />





<meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="用Unity3D实现【智能巡逻兵】"/>
<meta name="twitter:description" content="用Unity3D实现【智能巡逻兵】  作业要求  游戏设计要求：智能巡逻兵  创建一个地图和若干巡逻兵(使用动画)； 每个巡逻兵走一个3~5个边的凸多边型，位置数据是相对地址。即每次确定下一个目标位置，用自己当前位置为原点计算； 巡逻兵碰撞到障碍物，则会自动选下一个点为目标； 巡逻兵在设定范围内感知到玩家，会自动追击玩家； 失去玩家目标后，继续巡逻； 计分：玩家每次甩掉一个巡逻兵计一分，与巡逻兵碰撞游戏结束；   程序设计要求：  必须使用订阅与发布模式传消息  Subject（：OnLostGoal）、Publisher、Subscriber   工厂模式生产巡逻兵    项目文档 - 智能巡逻兵 项目仓库：https://gitee.com/WondrousWisdomcard/unity3d-homework
一、游戏规则 开始游戏后，玩家进入有16个房间的密室，除了玩家开始所在的餐厅之外，每个房间有若干宝石和幽灵，玩家需要集齐所有水晶来打开驱除幽灵，但要小心，不要被幽灵抓到。
游戏开始自动计时，没有时间限制，每摆脱一个幽灵可以获得一分，16个房间中随机散布着10个不同的宝石，除了邪恶的钟表房间有5个幽灵和餐厅没有幽灵之外，其余的房间只有3个幽灵，注意，每个房间东西南北的四个缝隙可能会被柜子或者壁橱堵住。
二、游戏效果 2.1 游戏效果         2.2 视频演示 视频地址：https://www.bilibili.com/video/BV1QQ4y1v7Ag?spm_id_from=333.999.0.0 （不知怎么紫的发亮）
三、项目配置过程   到 AssetStore 下载：
  游戏素材：3D Beginner: Tutorial Resources
  宝石素材：Simple Gems Ultimate Animated Customizable Pack
            直接复制以下代码到项目中：代码"/>







<script src="/vendor/js/jquery.min.js" ></script>
<script src="/vendor/js/popper.min.js" ></script>
<script src="/vendor/js/bootstrap.min.js" ></script>
<script src="/vendor/js/smooth-scroll.polyfills.min.js" ></script>
<link type="text/css" rel="stylesheet" href="/vendor/css/bootstrap.min.css">
<script src="/vendor/js/vue.min.js" ></script>






<link rel="stylesheet" href="https://wondrouswisdomcard.gitee.io/scss/journal.min.c116bc90d171283f099f173854157ec8f183f9073b93443b2c8ad82899ee9025.css" integrity="sha256-wRa8kNFxKD8Jnxc4VBV&#43;yPGD&#43;Qc7k0Q7LIrYKJnukCU=" media="screen">



<link rel="stylesheet" href="https://wondrouswisdomcard.gitee.io/scss/dark-mode.min.552aae4638a84aa57cf0b195750a49ea9131d3bb621d1ed3ebc9b14b18166536.css" integrity="sha256-VSquRjioSqV88LGVdQpJ6pEx07tiHR7T68mxSxgWZTY=" media="screen">


<script src="https://wondrouswisdomcard.gitee.io/js/loadCSS.js"></script>
<script>
  loadCSS("https://fonts.googleapis.com/css?family=Lora|Montserrat|Fira+Mono|Noto+Serif+SC|Material+Icons");
</script>




  
    <script src="https://wondrouswisdomcard.gitee.io/js/toc-collapse.js"></script>
  



<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css">
<script src="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js"></script>
<script src="/vendor/js/md5.min.js"></script>
<script>
  var gitalk = new Gitalk({
  clientID: 'your client id',
  clientSecret: 'your client secret',
  repo: 'repo name',
  owner: 'user',
  admin: ['user'],
  id: md5(location.pathname),
  distractionFreeMode: 'false'
  });
  window.onload = function () {
        gitalk.render('gitalk-container')
  }
</script>







<script src="https://cdn.jsdelivr.net/npm/vue-disqus@3/dist/vue-disqus.js"></script>


</head>
<body>
    	<div id="app"><div ref="sideContainer" class="side-container">
    
    <a class="a-block nav-head false" href="https://wondrouswisdomcard.gitee.io">
    
        <div class="nav-title">
            Wondrous WindomCard&#39;s Blog
        </div>
        
        <div class="nav-subtitle">
            Themed by Diary.
        </div>
        
    </a>

    <div class="nav-link-list">
        
        
            
            
            
                
            
            
            
            <a class="a-block nav-link-item active" href="/posts">
                Archive
            </a>
            
        
            
            
            
            
            
            <a class="a-block nav-link-item false" href="/categories">
                Categories
            </a>
            
        
            
            
            
            
            
            <a class="a-block nav-link-item false" href="/tags">
                Tags
            </a>
            
        
            
            
            
            
            
            <a class="a-block nav-link-item false" href="/index.xml">
                RSS Feed
            </a>
            
        
    </div>

    

    <div class="nav-footer">
        
Hugo Theme <a href="https://github.com/amazingrise/hugo-theme-diary">Diary</a> by <a href="https://amazingrise.net">Rise</a>
<br>
Ported from <a href="https://mak1t0.cc/" target="_blank" rel="noreferrer noopener">Makito</a>'s <a href="https://github.com/SumiMakito/hexo-theme-journal/" target="_blank" rel="noreferrer noopener">Journal.</a> <br>
<br>

&copy;
	
	This is a customized copyright.
	

    </div>
    
</div><div ref="extraContainer" class="extra-container">
    
    
    <div class="toc animated-visibility" :class="{ invisible: scrollY <= 140 }">


	<div class="toc-content">
	
		
		
		
		<center>- CATALOG -</center>
		
		
		<ul>
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#%e7%94%a8unity3d%e5%ae%9e%e7%8e%b0%e6%99%ba%e8%83%bd%e5%b7%a1%e9%80%bb%e5%85%b5" onclick="onNavClick(`#用unity3d实现智能巡逻兵-nav`)" id="用unity3d实现智能巡逻兵-nav">
									用Unity3D实现【智能巡逻兵】
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#%e4%bd%9c%e4%b8%9a%e8%a6%81%e6%b1%82" onclick="onNavClick(`#作业要求-nav`)" id="作业要求-nav">
									作业要求
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#%e9%a1%b9%e7%9b%ae%e6%96%87%e6%a1%a3---%e6%99%ba%e8%83%bd%e5%b7%a1%e9%80%bb%e5%85%b5" onclick="onNavClick(`#项目文档---智能巡逻兵-nav`)" id="项目文档---智能巡逻兵-nav">
									项目文档 - 智能巡逻兵
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#%e4%b8%80%e6%b8%b8%e6%88%8f%e8%a7%84%e5%88%99" onclick="onNavClick(`#一游戏规则-nav`)" id="一游戏规则-nav">
									一、游戏规则
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#%e4%ba%8c%e6%b8%b8%e6%88%8f%e6%95%88%e6%9e%9c" onclick="onNavClick(`#二游戏效果-nav`)" id="二游戏效果-nav">
									二、游戏效果
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#21-%e6%b8%b8%e6%88%8f%e6%95%88%e6%9e%9c" onclick="onNavClick(`#21-游戏效果-nav`)" id="21-游戏效果-nav">
									2.1 游戏效果
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#22-%e8%a7%86%e9%a2%91%e6%bc%94%e7%a4%ba" onclick="onNavClick(`#22-视频演示-nav`)" id="22-视频演示-nav">
									2.2 视频演示
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
							
								</ul>
							
						
						
						
							<li>
								<a href="#%e4%b8%89%e9%a1%b9%e7%9b%ae%e9%85%8d%e7%bd%ae%e8%bf%87%e7%a8%8b" onclick="onNavClick(`#三项目配置过程-nav`)" id="三项目配置过程-nav">
									三、项目配置过程
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#%e5%9b%9b%e5%ae%9e%e7%8e%b0%e6%80%9d%e8%b7%af" onclick="onNavClick(`#四实现思路-nav`)" id="四实现思路-nav">
									四、实现思路
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#41-%e6%88%bf%e9%97%b4%e7%9a%84%e5%88%92%e5%88%86%e4%b8%8e%e5%ae%9e%e7%8e%b0" onclick="onNavClick(`#41-房间的划分与实现-nav`)" id="41-房间的划分与实现-nav">
									4.1 房间的划分与实现
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#42-animator%e7%9a%84%e5%ae%9e%e7%8e%b0" onclick="onNavClick(`#42-animator的实现-nav`)" id="42-animator的实现-nav">
									4.2 Animator的实现
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#421-playeranimator" onclick="onNavClick(`#421-playeranimator-nav`)" id="421-playeranimator-nav">
									4.2.1 PlayerAnimator
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#422-ghostanimator" onclick="onNavClick(`#422-ghostanimator-nav`)" id="422-ghostanimator-nav">
									4.2.2 GhostAnimator
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
							
								</ul>
							
								</ul>
							
						
						
						
							<li>
								<a href="#%e4%ba%94%e6%a8%a1%e5%9d%97%e4%bb%8b%e7%bb%8d--%e6%a0%b8%e5%bf%83%e7%ae%97%e6%b3%95" onclick="onNavClick(`#五模块介绍--核心算法-nav`)" id="五模块介绍--核心算法-nav">
									五、模块介绍 &amp; 核心算法
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#51-model%e6%a8%a1%e5%9d%97" onclick="onNavClick(`#51-model模块-nav`)" id="51-model模块-nav">
									5.1. Model模块
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#511-gem-%e5%ae%9d%e7%9f%b3" onclick="onNavClick(`#511-gem-宝石-nav`)" id="511-gem-宝石-nav">
									5.1.1 Gem 宝石
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#512-ghost-%e5%b9%bd%e7%81%b5" onclick="onNavClick(`#512-ghost-幽灵-nav`)" id="512-ghost-幽灵-nav">
									5.1.2 Ghost 幽灵
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#513-player-%e7%8e%a9%e5%ae%b6" onclick="onNavClick(`#513-player-玩家-nav`)" id="513-player-玩家-nav">
									5.1.3 Player 玩家
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
							
								</ul>
							
						
						
						
							<li>
								<a href="#52-controller%e6%a8%a1%e5%9d%97" onclick="onNavClick(`#52-controller模块-nav`)" id="52-controller模块-nav">
									5.2 Controller模块
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#521-cameracontroller-%e6%91%84%e5%83%8f%e5%a4%b4%e6%8e%a7%e5%88%b6%e5%99%a8" onclick="onNavClick(`#521-cameracontroller-摄像头控制器-nav`)" id="521-cameracontroller-摄像头控制器-nav">
									5.2.1 CameraController 摄像头控制器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#522-firstcontroller-%e4%b8%bb%e6%8e%a7%e5%88%b6%e5%99%a8" onclick="onNavClick(`#522-firstcontroller-主控制器-nav`)" id="522-firstcontroller-主控制器-nav">
									5.2.2 FirstController 主控制器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#523-gameeventmanager-%e4%ba%8b%e4%bb%b6%e7%ae%a1%e7%90%86%e5%99%a8" onclick="onNavClick(`#523-gameeventmanager-事件管理器-nav`)" id="523-gameeventmanager-事件管理器-nav">
									5.2.3 GameEventManager 事件管理器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#524-playercontroller-%e7%8e%a9%e5%ae%b6%e8%bf%90%e5%8a%a8%e6%8e%a7%e5%88%b6%e5%99%a8" onclick="onNavClick(`#524-playercontroller-玩家运动控制器-nav`)" id="524-playercontroller-玩家运动控制器-nav">
									5.2.4 PlayerController 玩家运动控制器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#525-scorerecorder-%e8%ae%a1%e5%88%86%e6%9d%bf" onclick="onNavClick(`#525-scorerecorder-计分板-nav`)" id="525-scorerecorder-计分板-nav">
									5.2.5 ScoreRecorder 计分板
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
							
								</ul>
							
						
						
						
							<li>
								<a href="#53-sensor%e6%a8%a1%e5%9d%97" onclick="onNavClick(`#53-sensor模块-nav`)" id="53-sensor模块-nav">
									5.3 Sensor模块
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#531-gemcollidesensor-%e5%ae%9d%e7%9f%b3%e7%a2%b0%e6%92%9e%e6%a3%80%e6%b5%8b%e5%99%a8" onclick="onNavClick(`#531-gemcollidesensor-宝石碰撞检测器-nav`)" id="531-gemcollidesensor-宝石碰撞检测器-nav">
									5.3.1 GemCollideSensor 宝石碰撞检测器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#532-ghostcollidesensor-%e5%b9%bd%e7%81%b5%e7%a2%b0%e6%92%9e%e6%a3%80%e6%b5%8b%e5%99%a8" onclick="onNavClick(`#532-ghostcollidesensor-幽灵碰撞检测器-nav`)" id="532-ghostcollidesensor-幽灵碰撞检测器-nav">
									5.3.2 GhostCollideSensor 幽灵碰撞检测器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#533-inrangesensor-%e5%b9%bd%e7%81%b5%e8%8c%83%e5%9b%b4%e6%84%9f%e7%9f%a5%e5%99%a8" onclick="onNavClick(`#533-inrangesensor-幽灵范围感知器-nav`)" id="533-inrangesensor-幽灵范围感知器-nav">
									5.3.3 InRangeSensor 幽灵范围感知器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#534-roomsensor-%e6%88%bf%e9%97%b4%e6%a3%80%e6%b5%8b%e5%99%a8" onclick="onNavClick(`#534-roomsensor-房间检测器-nav`)" id="534-roomsensor-房间检测器-nav">
									5.3.4 RoomSensor 房间检测器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
							
								</ul>
							
						
						
						
							<li>
								<a href="#54-action%e6%a8%a1%e5%9d%97" onclick="onNavClick(`#54-action模块-nav`)" id="54-action模块-nav">
									5.4 Action模块
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#541-ghostwalkaction-%e5%b9%bd%e7%81%b5%e5%8a%a8%e4%bd%9c---%e9%97%b2%e9%80%9b" onclick="onNavClick(`#541-ghostwalkaction-幽灵动作---闲逛-nav`)" id="541-ghostwalkaction-幽灵动作---闲逛-nav">
									5.4.1 GhostWalkAction 幽灵动作 - 闲逛
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#542-ghostfollowaction-%e5%b9%bd%e7%81%b5%e5%8a%a8%e4%bd%9c---%e8%bf%bd%e5%87%bb" onclick="onNavClick(`#542-ghostfollowaction-幽灵动作---追击-nav`)" id="542-ghostfollowaction-幽灵动作---追击-nav">
									5.4.2 GhostFollowAction 幽灵动作 - 追击
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#543-ghostactionmanager-%e5%b9%bd%e7%81%b5%e5%8a%a8%e4%bd%9c%e7%ae%a1%e7%90%86%e5%99%a8" onclick="onNavClick(`#543-ghostactionmanager-幽灵动作管理器-nav`)" id="543-ghostactionmanager-幽灵动作管理器-nav">
									5.4.3 GhostActionManager 幽灵动作管理器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
							
								</ul>
							
						
						
						
							<li>
								<a href="#55-gui%e6%a8%a1%e5%9d%97" onclick="onNavClick(`#55-gui模块-nav`)" id="55-gui模块-nav">
									5.5 GUI模块
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#551-usergui-%e7%95%8c%e9%9d%a2%e4%ba%a4%e4%ba%92" onclick="onNavClick(`#551-usergui-界面交互-nav`)" id="551-usergui-界面交互-nav">
									5.5.1 UserGUI 界面交互
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
							
								</ul>
							
								</ul>
							
						
						
						
							<li>
								<a href="#%e5%85%ad%e5%ae%9e%e7%8e%b0%e8%bf%87%e7%a8%8b%e4%b8%ad%e9%97%ae%e9%a2%98" onclick="onNavClick(`#六实现过程中问题-nav`)" id="六实现过程中问题-nav">
									六、实现过程中问题
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#61-scenemanager%e4%b8%8e%e5%85%89%e7%ba%bf%e6%b8%b2%e6%9f%93" onclick="onNavClick(`#61-scenemanager与光线渲染-nav`)" id="61-scenemanager与光线渲染-nav">
									6.1 SceneManager与光线渲染
								</a>
							</li>
						
						
					
				
			
		</ul>
	</div>

</div>
    
    <div class="pagination">
        <a id="globalBackToTop" class="pagination-action animated-visibility" href="#top" :class="{ invisible: scrollY == 0 }">
            <i class="material-icons pagination-action-icon">
                keyboard_arrow_up
            </i>
        </a>
        
        <a class="pagination-action" v-on:click="toggleDarkMode">
            <i class="material-icons pagination-action-icon" v-if="isDarkMode">
                brightness_4
            </i>
            <i class="material-icons pagination-action-icon" v-else="isDarkMode">
                brightness_7
            </i>
        </a>
        
        
    </div>
</div>
<div class="single-column-drawer-container" ref="drawer"
     v-bind:class="{ 'single-column-drawer-container-active': isDrawerOpen }">
    <div class="drawer-content">
        <div class="drawer-menu">
            
            
            
                
                
                
                    
                
                
                
                <a class="a-block drawer-menu-item active" href="/posts">
                    Archive
                </a>
                
            
                
                
                
                
                
                <a class="a-block drawer-menu-item false" href="/categories">
                    Categories
                </a>
                
            
                
                
                
                
                
                <a class="a-block drawer-menu-item false" href="/tags">
                    Tags
                </a>
                
            
                
                
                
                
                
                <a class="a-block drawer-menu-item false" href="/index.xml">
                    RSS Feed
                </a>
                
            
            
            <div class="toc">


	<div class="toc-content">
	
		
		
		
		<center>- CATALOG -</center>
		
		
		<ul>
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#%e7%94%a8unity3d%e5%ae%9e%e7%8e%b0%e6%99%ba%e8%83%bd%e5%b7%a1%e9%80%bb%e5%85%b5" onclick="onNavClick(`#用unity3d实现智能巡逻兵-nav`)" id="用unity3d实现智能巡逻兵-nav">
									用Unity3D实现【智能巡逻兵】
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#%e4%bd%9c%e4%b8%9a%e8%a6%81%e6%b1%82" onclick="onNavClick(`#作业要求-nav`)" id="作业要求-nav">
									作业要求
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#%e9%a1%b9%e7%9b%ae%e6%96%87%e6%a1%a3---%e6%99%ba%e8%83%bd%e5%b7%a1%e9%80%bb%e5%85%b5" onclick="onNavClick(`#项目文档---智能巡逻兵-nav`)" id="项目文档---智能巡逻兵-nav">
									项目文档 - 智能巡逻兵
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#%e4%b8%80%e6%b8%b8%e6%88%8f%e8%a7%84%e5%88%99" onclick="onNavClick(`#一游戏规则-nav`)" id="一游戏规则-nav">
									一、游戏规则
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#%e4%ba%8c%e6%b8%b8%e6%88%8f%e6%95%88%e6%9e%9c" onclick="onNavClick(`#二游戏效果-nav`)" id="二游戏效果-nav">
									二、游戏效果
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#21-%e6%b8%b8%e6%88%8f%e6%95%88%e6%9e%9c" onclick="onNavClick(`#21-游戏效果-nav`)" id="21-游戏效果-nav">
									2.1 游戏效果
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#22-%e8%a7%86%e9%a2%91%e6%bc%94%e7%a4%ba" onclick="onNavClick(`#22-视频演示-nav`)" id="22-视频演示-nav">
									2.2 视频演示
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
							
								</ul>
							
						
						
						
							<li>
								<a href="#%e4%b8%89%e9%a1%b9%e7%9b%ae%e9%85%8d%e7%bd%ae%e8%bf%87%e7%a8%8b" onclick="onNavClick(`#三项目配置过程-nav`)" id="三项目配置过程-nav">
									三、项目配置过程
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#%e5%9b%9b%e5%ae%9e%e7%8e%b0%e6%80%9d%e8%b7%af" onclick="onNavClick(`#四实现思路-nav`)" id="四实现思路-nav">
									四、实现思路
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#41-%e6%88%bf%e9%97%b4%e7%9a%84%e5%88%92%e5%88%86%e4%b8%8e%e5%ae%9e%e7%8e%b0" onclick="onNavClick(`#41-房间的划分与实现-nav`)" id="41-房间的划分与实现-nav">
									4.1 房间的划分与实现
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#42-animator%e7%9a%84%e5%ae%9e%e7%8e%b0" onclick="onNavClick(`#42-animator的实现-nav`)" id="42-animator的实现-nav">
									4.2 Animator的实现
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#421-playeranimator" onclick="onNavClick(`#421-playeranimator-nav`)" id="421-playeranimator-nav">
									4.2.1 PlayerAnimator
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#422-ghostanimator" onclick="onNavClick(`#422-ghostanimator-nav`)" id="422-ghostanimator-nav">
									4.2.2 GhostAnimator
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
							
								</ul>
							
								</ul>
							
						
						
						
							<li>
								<a href="#%e4%ba%94%e6%a8%a1%e5%9d%97%e4%bb%8b%e7%bb%8d--%e6%a0%b8%e5%bf%83%e7%ae%97%e6%b3%95" onclick="onNavClick(`#五模块介绍--核心算法-nav`)" id="五模块介绍--核心算法-nav">
									五、模块介绍 &amp; 核心算法
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#51-model%e6%a8%a1%e5%9d%97" onclick="onNavClick(`#51-model模块-nav`)" id="51-model模块-nav">
									5.1. Model模块
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#511-gem-%e5%ae%9d%e7%9f%b3" onclick="onNavClick(`#511-gem-宝石-nav`)" id="511-gem-宝石-nav">
									5.1.1 Gem 宝石
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#512-ghost-%e5%b9%bd%e7%81%b5" onclick="onNavClick(`#512-ghost-幽灵-nav`)" id="512-ghost-幽灵-nav">
									5.1.2 Ghost 幽灵
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#513-player-%e7%8e%a9%e5%ae%b6" onclick="onNavClick(`#513-player-玩家-nav`)" id="513-player-玩家-nav">
									5.1.3 Player 玩家
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
							
								</ul>
							
						
						
						
							<li>
								<a href="#52-controller%e6%a8%a1%e5%9d%97" onclick="onNavClick(`#52-controller模块-nav`)" id="52-controller模块-nav">
									5.2 Controller模块
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#521-cameracontroller-%e6%91%84%e5%83%8f%e5%a4%b4%e6%8e%a7%e5%88%b6%e5%99%a8" onclick="onNavClick(`#521-cameracontroller-摄像头控制器-nav`)" id="521-cameracontroller-摄像头控制器-nav">
									5.2.1 CameraController 摄像头控制器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#522-firstcontroller-%e4%b8%bb%e6%8e%a7%e5%88%b6%e5%99%a8" onclick="onNavClick(`#522-firstcontroller-主控制器-nav`)" id="522-firstcontroller-主控制器-nav">
									5.2.2 FirstController 主控制器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#523-gameeventmanager-%e4%ba%8b%e4%bb%b6%e7%ae%a1%e7%90%86%e5%99%a8" onclick="onNavClick(`#523-gameeventmanager-事件管理器-nav`)" id="523-gameeventmanager-事件管理器-nav">
									5.2.3 GameEventManager 事件管理器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#524-playercontroller-%e7%8e%a9%e5%ae%b6%e8%bf%90%e5%8a%a8%e6%8e%a7%e5%88%b6%e5%99%a8" onclick="onNavClick(`#524-playercontroller-玩家运动控制器-nav`)" id="524-playercontroller-玩家运动控制器-nav">
									5.2.4 PlayerController 玩家运动控制器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#525-scorerecorder-%e8%ae%a1%e5%88%86%e6%9d%bf" onclick="onNavClick(`#525-scorerecorder-计分板-nav`)" id="525-scorerecorder-计分板-nav">
									5.2.5 ScoreRecorder 计分板
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
							
								</ul>
							
						
						
						
							<li>
								<a href="#53-sensor%e6%a8%a1%e5%9d%97" onclick="onNavClick(`#53-sensor模块-nav`)" id="53-sensor模块-nav">
									5.3 Sensor模块
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#531-gemcollidesensor-%e5%ae%9d%e7%9f%b3%e7%a2%b0%e6%92%9e%e6%a3%80%e6%b5%8b%e5%99%a8" onclick="onNavClick(`#531-gemcollidesensor-宝石碰撞检测器-nav`)" id="531-gemcollidesensor-宝石碰撞检测器-nav">
									5.3.1 GemCollideSensor 宝石碰撞检测器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#532-ghostcollidesensor-%e5%b9%bd%e7%81%b5%e7%a2%b0%e6%92%9e%e6%a3%80%e6%b5%8b%e5%99%a8" onclick="onNavClick(`#532-ghostcollidesensor-幽灵碰撞检测器-nav`)" id="532-ghostcollidesensor-幽灵碰撞检测器-nav">
									5.3.2 GhostCollideSensor 幽灵碰撞检测器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#533-inrangesensor-%e5%b9%bd%e7%81%b5%e8%8c%83%e5%9b%b4%e6%84%9f%e7%9f%a5%e5%99%a8" onclick="onNavClick(`#533-inrangesensor-幽灵范围感知器-nav`)" id="533-inrangesensor-幽灵范围感知器-nav">
									5.3.3 InRangeSensor 幽灵范围感知器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#534-roomsensor-%e6%88%bf%e9%97%b4%e6%a3%80%e6%b5%8b%e5%99%a8" onclick="onNavClick(`#534-roomsensor-房间检测器-nav`)" id="534-roomsensor-房间检测器-nav">
									5.3.4 RoomSensor 房间检测器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
							
								</ul>
							
						
						
						
							<li>
								<a href="#54-action%e6%a8%a1%e5%9d%97" onclick="onNavClick(`#54-action模块-nav`)" id="54-action模块-nav">
									5.4 Action模块
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#541-ghostwalkaction-%e5%b9%bd%e7%81%b5%e5%8a%a8%e4%bd%9c---%e9%97%b2%e9%80%9b" onclick="onNavClick(`#541-ghostwalkaction-幽灵动作---闲逛-nav`)" id="541-ghostwalkaction-幽灵动作---闲逛-nav">
									5.4.1 GhostWalkAction 幽灵动作 - 闲逛
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#542-ghostfollowaction-%e5%b9%bd%e7%81%b5%e5%8a%a8%e4%bd%9c---%e8%bf%bd%e5%87%bb" onclick="onNavClick(`#542-ghostfollowaction-幽灵动作---追击-nav`)" id="542-ghostfollowaction-幽灵动作---追击-nav">
									5.4.2 GhostFollowAction 幽灵动作 - 追击
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
						
							<li>
								<a href="#543-ghostactionmanager-%e5%b9%bd%e7%81%b5%e5%8a%a8%e4%bd%9c%e7%ae%a1%e7%90%86%e5%99%a8" onclick="onNavClick(`#543-ghostactionmanager-幽灵动作管理器-nav`)" id="543-ghostactionmanager-幽灵动作管理器-nav">
									5.4.3 GhostActionManager 幽灵动作管理器
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
							
								</ul>
							
						
						
						
							<li>
								<a href="#55-gui%e6%a8%a1%e5%9d%97" onclick="onNavClick(`#55-gui模块-nav`)" id="55-gui模块-nav">
									5.5 GUI模块
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#551-usergui-%e7%95%8c%e9%9d%a2%e4%ba%a4%e4%ba%92" onclick="onNavClick(`#551-usergui-界面交互-nav`)" id="551-usergui-界面交互-nav">
									5.5.1 UserGUI 界面交互
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
							
								</ul>
							
								</ul>
							
						
						
						
							<li>
								<a href="#%e5%85%ad%e5%ae%9e%e7%8e%b0%e8%bf%87%e7%a8%8b%e4%b8%ad%e9%97%ae%e9%a2%98" onclick="onNavClick(`#六实现过程中问题-nav`)" id="六实现过程中问题-nav">
									六、实现过程中问题
								</a>
							</li>
						
						
					
				
			
				
				
					
						
						
						
						
							
								<ul class="collapse" data-toggle="collapse">
							
						
						
							<li>
								<a href="#61-scenemanager%e4%b8%8e%e5%85%89%e7%ba%bf%e6%b8%b2%e6%9f%93" onclick="onNavClick(`#61-scenemanager与光线渲染-nav`)" id="61-scenemanager与光线渲染-nav">
									6.1 SceneManager与光线渲染
								</a>
							</li>
						
						
					
				
			
		</ul>
	</div>

</div>
            
        </div>
    </div>
</div>
<transition name="fade">
    <div v-bind:class="{ 'single-column-drawer-mask': mounted }" v-if="isDrawerOpen" v-on:click="toggleDrawer"></div>
</transition>
<nav ref="navBar" class="navbar sticky-top navbar-light single-column-nav-container">
    <div ref="navBackground" class="nav-background"></div>
    <div class="container container-narrow nav-content">
        <button id="nav_dropdown_btn" class="nav-dropdown-toggle" type="button" v-on:click="toggleDrawer">
            <i class="material-icons">
                menu
            </i>
        </button>
        <a ref="navTitle" class="navbar-brand" href="https://wondrouswisdomcard.gitee.io">
            Wondrous WindomCard&#39;s Blog
        </a>
        
        <button type="button" class="nav-darkmode-toggle" v-on:click="toggleDarkMode">
            <i class="material-icons" v-if="isDarkMode">
                brightness_4
            </i>
            <i class="material-icons" v-else="isDarkMode">
                brightness_7
            </i>
        </button>
        
    </div>
</nav>
<div class="single-column-header-container" ref="pageHead"
     v-bind:style="{ transform: 'translateZ(0px) translateY('+.3*scrollY+'px)', opacity: 1-navOpacity }">
    <a href="https://wondrouswisdomcard.gitee.io">
        <div class="single-column-header-title">Wondrous WindomCard&#39;s Blog</div>
        
        <div class="single-column-header-subtitle">Themed by Diary.</div>
        

    </a>
</div>

            <div id="content">
<div ref="streamContainer" class="stream-container">
    <div class="post-list-container post-list-container-shadow">
        <div class="post">
            
            
            

            <div class="post-head-wrapper-text-only"
                
            >
                <div class="post-title">
                    用Unity3D实现【智能巡逻兵】
                    
                    <div class="post-meta">
                        
                        <time itemprop="datePublished">
                            2021-11-28 00:00
                        </time>
                        

                        
                            <i class="material-icons" style="">folder</i>
                                <a href="/categories/">[Unity3D]</a>
                                &nbsp;
                        

                        
                            <i class="material-icons" style="">label</i>
                            
                                <a href="/tags/unity3d">Unity3D</a>
                                &nbsp;
                            
                        
                        
                    </div>
                </div>
            </div>
            
            <div class="post-body-wrapper">
                
                <div class="post-body" v-pre>
                
                    <h1 id="用unity3d实现智能巡逻兵">用Unity3D实现【智能巡逻兵】</h1>
<hr>
<h1 id="作业要求">作业要求</h1>
<ul>
<li>游戏设计要求：<strong>智能巡逻兵</strong>
<ul>
<li>创建一个地图和若干巡逻兵(使用动画)；</li>
<li>每个巡逻兵走一个3~5个边的凸多边型，位置数据是相对地址。即每次确定下一个目标位置，用自己当前位置为原点计算；</li>
<li>巡逻兵碰撞到障碍物，则会自动选下一个点为目标；</li>
<li>巡逻兵在设定范围内感知到玩家，会自动追击玩家；</li>
<li>失去玩家目标后，继续巡逻；</li>
<li>计分：玩家每次甩掉一个巡逻兵计一分，与巡逻兵碰撞游戏结束；</li>
</ul>
</li>
<li>程序设计要求：
<ul>
<li>必须使用订阅与发布模式传消息
<ul>
<li>Subject（：OnLostGoal）、Publisher、Subscriber</li>
</ul>
</li>
<li>工厂模式生产巡逻兵</li>
</ul>
</li>
</ul>
<h1 id="项目文档---智能巡逻兵">项目文档 - 智能巡逻兵</h1>
<p>项目仓库：https://gitee.com/WondrousWisdomcard/unity3d-homework</p>
<h2 id="一游戏规则">一、游戏规则</h2>
<p>开始游戏后，玩家进入有16个房间的密室，除了玩家开始所在的餐厅之外，每个房间有若干宝石和幽灵，玩家需要集齐所有水晶来打开驱除幽灵，但要小心，不要被幽灵抓到。</p>
<p>游戏开始自动计时，没有时间限制，每摆脱一个幽灵可以获得一分，16个房间中随机散布着10个不同的宝石，除了邪恶的钟表房间有5个幽灵和餐厅没有幽灵之外，其余的房间只有3个幽灵，注意，每个房间东西南北的四个缝隙可能会被柜子或者壁橱堵住。</p>
<h2 id="二游戏效果">二、游戏效果</h2>
<h3 id="21-游戏效果">2.1 游戏效果</h3>
<table>
<thead>
<tr>
<th><img src="../../images/unity3d4/1.png" alt=""></th>
<th><img src="../../images/unity3d4/2.png" alt=""></th>
</tr>
</thead>
</table>
<h3 id="22-视频演示">2.2 视频演示</h3>
<p><strong>视频地址</strong>：https://www.bilibili.com/video/BV1QQ4y1v7Ag?spm_id_from=333.999.0.0 （不知怎么紫的发亮）</p>
<h2 id="三项目配置过程">三、项目配置过程</h2>
<ol>
<li>
<p><strong>到 AssetStore 下载：</strong></p>
<ol>
<li>
<p><a href="https://assetstore.unity.com/packages/templates/tutorials/3d-beginner-tutorial-resources-143848">游戏素材</a>：<strong>3D Beginner: Tutorial Resources</strong></p>
</li>
<li>
<p><a href="https://assetstore.unity.com/packages/3d/props/simple-gems-ultimate-animated-customizable-pack-73764">宝石素材</a>：<strong>Simple Gems Ultimate Animated Customizable Pack</strong></p>
<table>
<thead>
<tr>
<th><img src="../../images/unity3d4/3.png" alt="../../images/unity3d4-20211128214604522"></th>
<th><img src="../../images/unity3d4/4.png" alt="../../images/unity3d4-20211128214651512"></th>
</tr>
</thead>
</table>
</li>
</ol>
</li>
<li>
<p><strong>直接复制以下代码到项目中：<a href="https://gitee.com/WondrousWisdomcard/unity3d-homework/tree/master/Homework05/%E9%A1%B9%E7%9B%AE%E4%BB%A3%E7%A0%81/Assets">代码</a></strong></p>
</li>
</ol>
<h2 id="四实现思路">四、实现思路</h2>
<h3 id="41-房间的划分与实现">4.1 房间的划分与实现</h3>
<ul>
<li>
<p>我们划分了 4X4 个房间，每个房间为正方形，边长为10，不同的房间可能会有一些装饰（也是游戏过程中的障碍物），玩家初始位于的房间是7号，也就是餐厅，各个房间的编号如下：</p>
<p><img src="../../images/unity3d4/11.png" alt="未命名文件"></p>
</li>
</ul>
<h3 id="42-animator的实现">4.2 Animator的实现</h3>
<p><strong>位于：Animation/Animator 中，受限于素材动画有限，Animator比较简单</strong></p>
<h4 id="421-playeranimator">4.2.1 PlayerAnimator</h4>
<p><img src="../../images/unity3d4/9.png" alt="../../images/unity3d4-20211129090231659"></p>
<h4 id="422-ghostanimator">4.2.2 GhostAnimator</h4>
<p><img src="../../images/unity3d4/10.png" alt="../../images/unity3d4-20211129090213938"></p>
<h2 id="五模块介绍--核心算法">五、模块介绍 &amp; 核心算法</h2>
<h3 id="51-model模块">5.1. Model模块</h3>
<p><strong>位于：MyScripts/Model，定义游戏对象的数据和工厂类。</strong></p>
<h4 id="511-gem-宝石">5.1.1 Gem 宝石</h4>
<ul>
<li>
<p><strong>GemData</strong>：宝石数据</p>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">GemData</span> : MonoBehaviour{
    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">int</span> gemID;           <span style="color:#228b22">// 宝石编号
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">int</span> gemRoomID;       <span style="color:#228b22">// 宝石所在房间
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">bool</span> isValid;        <span style="color:#228b22">// 是否还存在于地图中
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">bool</span> isCatch;        <span style="color:#228b22">// 玩家是否碰到宝石
</span><span style="color:#228b22"></span>}
</code></pre></div></li>
<li>
<p><strong>GemFactory</strong>：工厂类，生成宝石</p>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">GemFactory</span> : MonoBehaviour
{
    <span style="color:#8b008b;font-weight:bold">private</span> List&lt;GemData&gt; gems = <span style="color:#8b008b;font-weight:bold">new</span> List&lt;GemData&gt;();
  
    <span style="color:#00688b;font-weight:bold">string</span> gemsPath = <span style="color:#cd5555">&#34;MyPrefabs/Gem/Gem&#34;</span>;
    <span style="color:#00688b;font-weight:bold">string</span>[] gemsID = <span style="color:#8b008b;font-weight:bold">new</span> <span style="color:#00688b;font-weight:bold">string</span>[<span style="color:#b452cd">11</span>] {<span style="color:#cd5555">&#34;00&#34;</span>, <span style="color:#cd5555">&#34;01&#34;</span>, <span style="color:#cd5555">&#34;02&#34;</span>, <span style="color:#cd5555">&#34;03&#34;</span>, <span style="color:#cd5555">&#34;04&#34;</span>, <span style="color:#cd5555">&#34;05&#34;</span>, <span style="color:#cd5555">&#34;06&#34;</span>, <span style="color:#cd5555">&#34;07&#34;</span>, <span style="color:#cd5555">&#34;08&#34;</span>, <span style="color:#cd5555">&#34;09&#34;</span>, <span style="color:#cd5555">&#34;10&#34;</span>};
  
    <span style="color:#00688b;font-weight:bold">int</span>[] gemX = <span style="color:#8b008b;font-weight:bold">new</span> <span style="color:#00688b;font-weight:bold">int</span>[<span style="color:#b452cd">4</span>] {-<span style="color:#b452cd">15</span>, -<span style="color:#b452cd">5</span>, <span style="color:#b452cd">5</span>, <span style="color:#b452cd">15</span>};
    <span style="color:#00688b;font-weight:bold">int</span>[] gemZ = <span style="color:#8b008b;font-weight:bold">new</span> <span style="color:#00688b;font-weight:bold">int</span>[<span style="color:#b452cd">4</span>] {<span style="color:#b452cd">15</span>, <span style="color:#b452cd">5</span>, -<span style="color:#b452cd">5</span>, -<span style="color:#b452cd">15</span>};
  
    <span style="color:#228b22">// 返回指定房间内的宝石
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> GameObject GetGem(<span style="color:#00688b;font-weight:bold">int</span> roomID){
        <span style="color:#8b008b;font-weight:bold">if</span>(gems.Count != <span style="color:#b452cd">0</span>){
            <span style="color:#8b008b;font-weight:bold">foreach</span>(GemData existGem <span style="color:#8b008b;font-weight:bold">in</span> gems){
                <span style="color:#8b008b;font-weight:bold">if</span>(existGem.gemRoomID == roomID){
                    <span style="color:#8b008b;font-weight:bold">return</span> existGem.gameObject;
                }
            }
        }
        <span style="color:#8b008b;font-weight:bold">return</span> <span style="color:#8b008b;font-weight:bold">null</span>;
    }
  
    <span style="color:#228b22">// 创建指定ID和房间的宝石
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> GameObject GenGem(<span style="color:#00688b;font-weight:bold">int</span> gemID, <span style="color:#00688b;font-weight:bold">int</span> roomID){
  
        GameObject gem = <span style="color:#8b008b;font-weight:bold">null</span>;
          
        <span style="color:#228b22">// 给出宝石的预制路径、根据房间ID算出位置和Quaternion
</span><span style="color:#228b22"></span>        <span style="color:#00688b;font-weight:bold">string</span> gemPath = gemsPath + gemsID[gemID];
        <span style="color:#00688b;font-weight:bold">int</span> x = gemX[(roomID - <span style="color:#b452cd">1</span>) % <span style="color:#b452cd">4</span>];
        <span style="color:#00688b;font-weight:bold">int</span> z = gemZ[(roomID - <span style="color:#b452cd">1</span>) / <span style="color:#b452cd">4</span>];
        Vector3 pos = <span style="color:#8b008b;font-weight:bold">new</span> Vector3(x, <span style="color:#b452cd">1</span>, z);
        Quaternion rot = <span style="color:#8b008b;font-weight:bold">new</span> Quaternion(-<span style="color:#b452cd">0.707106829F</span>, <span style="color:#b452cd">0</span>, <span style="color:#b452cd">0</span>, <span style="color:#b452cd">0.707106829F</span>);
  
        <span style="color:#228b22">// 创建宝石对象
</span><span style="color:#228b22"></span>        gem = GameObject.Instantiate&lt;GameObject&gt;(Resources.Load&lt;GameObject&gt;(gemPath), pos, rot);
          
        <span style="color:#228b22">// 创建碰撞检测器
</span><span style="color:#228b22"></span>        gem.AddComponent&lt;GemCollideSensor&gt;();
  
        <span style="color:#228b22">// 为创建的宝石对象赋予数据
</span><span style="color:#228b22"></span>        gem.AddComponent&lt;GemData&gt;();
        <span style="color:#8b008b;font-weight:bold">if</span>(gem != <span style="color:#8b008b;font-weight:bold">null</span>){
            GemData gemData = gem.GetComponent&lt;GemData&gt;();
            gemData.gemID = gemID;
            gemData.gemRoomID = roomID;
            gemData.isValid = <span style="color:#8b008b;font-weight:bold">true</span>;
            gemData.isCatch = <span style="color:#8b008b;font-weight:bold">false</span>;
            gems.Add(gemData);
        }
        gem.SetActive(<span style="color:#8b008b;font-weight:bold">true</span>);
        <span style="color:#8b008b;font-weight:bold">return</span> gem;
    }
  
    <span style="color:#228b22">// 删除宝石
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> FreeGem(GameObject gem){
        <span style="color:#8b008b;font-weight:bold">foreach</span>(GemData gemData <span style="color:#8b008b;font-weight:bold">in</span> gems){
            <span style="color:#8b008b;font-weight:bold">if</span>(gemData.gameObject.GetInstanceID() == gem.GetInstanceID()){
                gem.SetActive(<span style="color:#8b008b;font-weight:bold">false</span>);
                gems.Remove(gemData);
                <span style="color:#8b008b;font-weight:bold">break</span>;
            }
        }
    }
}
</code></pre></div></li>
<li>
<p><strong>宝石预制</strong>：</p>
<p><img src="../../images/unity3d4/6.png" alt="../../images/unity3d4-20211128215207638"></p>
</li>
</ul>
<h4 id="512-ghost-幽灵">5.1.2 Ghost 幽灵</h4>
<ul>
<li>
<p><strong>GhostData</strong>：幽灵数据</p>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">GhostData</span> : MonoBehaviour{
    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">int</span> ghostRoomID;         <span style="color:#228b22">// 幽灵所在房间
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">int</span> eyeshot;             <span style="color:#228b22">// 幽灵感知半径
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">bool</span> isInRange;          <span style="color:#228b22">// 是否发现玩家
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">bool</span> isFollow;           <span style="color:#228b22">// 是否正在追击
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">bool</span> isCollided;         <span style="color:#228b22">// 是否抓到玩家
</span><span style="color:#228b22"></span>}
</code></pre></div></li>
<li>
<p><strong>GhostFactory</strong>：工厂类，生成幽灵</p>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">GhostFactory</span> : MonoBehaviour
{
    <span style="color:#8b008b;font-weight:bold">private</span> List&lt;GhostData&gt; ghostDatas = <span style="color:#8b008b;font-weight:bold">new</span> List&lt;GhostData&gt;(); 
    <span style="color:#00688b;font-weight:bold">int</span>[] ghostX = <span style="color:#8b008b;font-weight:bold">new</span> <span style="color:#00688b;font-weight:bold">int</span>[<span style="color:#b452cd">4</span>] {-<span style="color:#b452cd">15</span>, -<span style="color:#b452cd">5</span>, <span style="color:#b452cd">5</span>, <span style="color:#b452cd">15</span>};
    <span style="color:#00688b;font-weight:bold">int</span>[] ghostZ = <span style="color:#8b008b;font-weight:bold">new</span> <span style="color:#00688b;font-weight:bold">int</span>[<span style="color:#b452cd">4</span>] {<span style="color:#b452cd">15</span>, <span style="color:#b452cd">5</span>, -<span style="color:#b452cd">5</span>, -<span style="color:#b452cd">15</span>};
  
    <span style="color:#228b22">// 创建一个幽灵，i（行）j（列）组成房间号， dx dz 是相对于房间中心的位置
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> GameObject GenGhost(<span style="color:#00688b;font-weight:bold">int</span> i, <span style="color:#00688b;font-weight:bold">int</span> j, <span style="color:#00688b;font-weight:bold">int</span> dx, <span style="color:#00688b;font-weight:bold">int</span> dz){
        GameObject ghost = Instantiate(Resources.Load&lt;GameObject&gt;(<span style="color:#cd5555">&#34;MyPrefabs/Ghost&#34;</span>));
                 
        ghost.transform.position = <span style="color:#8b008b;font-weight:bold">new</span> Vector3(ghostX[i] + dx, <span style="color:#b452cd">0</span>, ghostZ[j] + dz);
          
        ghost.AddComponent&lt;GhostData&gt;();
          
        ghost.AddComponent&lt;GhostCollideSensor&gt;();
  
        ghost.transform.GetChild(<span style="color:#b452cd">0</span>).gameObject.AddComponent&lt;InRangeSensor&gt;();
        ghost.transform.GetChild(<span style="color:#b452cd">0</span>).gameObject.GetComponent&lt;InRangeSensor&gt;().ghost = ghost;
      
        GhostData ghostData = ghost.GetComponent&lt;GhostData&gt;();
        ghostData.ghostRoomID = i + j * <span style="color:#b452cd">4</span> + <span style="color:#b452cd">1</span>;
        ghostData.eyeshot = <span style="color:#b452cd">5</span>;
        ghostData.isInRange = <span style="color:#8b008b;font-weight:bold">false</span>;
        ghostData.isFollow = <span style="color:#8b008b;font-weight:bold">false</span>;
        ghostData.isCollided = <span style="color:#8b008b;font-weight:bold">false</span>;
  
        ghostDatas.Add(ghostData);
  
        <span style="color:#8b008b;font-weight:bold">return</span> ghost;
    }
      
}
</code></pre></div></li>
</ul>
<h4 id="513-player-玩家">5.1.3 Player 玩家</h4>
<ul>
<li>
<p><strong>PlayerData</strong>：玩家数据</p>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">PlayerData</span> : MonoBehaviour{
    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">int</span> playerRoomID;     <span style="color:#228b22">// 玩家所在房间
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">bool</span> alive;           <span style="color:#228b22">// 玩家是否存活
</span><span style="color:#228b22"></span>}
</code></pre></div></li>
<li>
<p><strong>PlayerFactory</strong>：玩家工厂</p>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">PlayerFactory</span> : MonoBehaviour
{
    <span style="color:#8b008b;font-weight:bold">private</span> PlayerData playerData;
    <span style="color:#8b008b;font-weight:bold">public</span> GameObject GenPlayer(){
  
        GameObject player = Instantiate(Resources.Load&lt;GameObject&gt;(<span style="color:#cd5555">&#34;MyPrefabs/JohnLemon&#34;</span>));
          
        player.AddComponent&lt;PlayerController&gt;();
        player.AddComponent&lt;PlayerData&gt;();
        playerData = player.GetComponent&lt;PlayerData&gt;();
          
        <span style="color:#228b22">// 开始房间：7
</span><span style="color:#228b22"></span>        playerData.playerRoomID = <span style="color:#b452cd">7</span>;
        playerData.alive = <span style="color:#8b008b;font-weight:bold">true</span>;
  
        <span style="color:#8b008b;font-weight:bold">return</span> player;
    }
}
</code></pre></div></li>
</ul>
<h3 id="52-controller模块">5.2 Controller模块</h3>
<p><strong>位于：MyScripts/Controller，各类控制器。</strong></p>
<h4 id="521-cameracontroller-摄像头控制器">5.2.1 CameraController 摄像头控制器</h4>
<ul>
<li>摄像头控制器需要手动挂载到主摄像机上，摄像头跟随玩家而移动，为了让镜头不猛烈晃动，我们对移动过程做平滑变换，让镜头的移动更加自然。</li>
</ul>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#8b008b;font-weight:bold">using</span> <span style="color:#008b45;text-decoration:underline">System.Collections</span>;
<span style="color:#8b008b;font-weight:bold">using</span> <span style="color:#008b45;text-decoration:underline">System.Collections.Generic</span>;
<span style="color:#8b008b;font-weight:bold">using</span> <span style="color:#008b45;text-decoration:underline">UnityEngine</span>;

<span style="color:#228b22">// 摄像头控制器：需要手动挂载到主摄像机上
</span><span style="color:#228b22"></span><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">CameraController</span> : MonoBehaviour
{
	<span style="color:#8b008b;font-weight:bold">public</span> GameObject player;
    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">float</span> distanceAway = <span style="color:#b452cd">3F</span>;		 	<span style="color:#228b22">// 摄像头离玩家的水平距离	
</span><span style="color:#228b22"></span>	<span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">float</span> distanceUp = <span style="color:#b452cd">3F</span>;			<span style="color:#228b22">// 摄像头离地面的垂直距离		
</span><span style="color:#228b22"></span>	<span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">float</span> smooth = <span style="color:#b452cd">2F</span>;				<span style="color:#228b22">// 平滑变换参数
</span><span style="color:#228b22"></span>		
	<span style="color:#8b008b;font-weight:bold">private</span> Vector3 m_TargetPosition;		<span style="color:#228b22">// 摄像头的位置
</span><span style="color:#228b22"></span>	
	Transform follow;        				<span style="color:#228b22">// 摄像头望向的位置
</span><span style="color:#228b22"></span>	
	<span style="color:#8b008b;font-weight:bold">void</span> Start(){
		
	}
	
	<span style="color:#8b008b;font-weight:bold">void</span> LateUpdate ()
	{
		follow = player.transform.GetChild(<span style="color:#b452cd">2</span>);
		
		<span style="color:#228b22">// 设置摄像头的目标位置
</span><span style="color:#228b22"></span>		m_TargetPosition = follow.position + Vector3.up * distanceUp - follow.forward * distanceAway;
		
		<span style="color:#228b22">// 对移动过程进行平滑变换
</span><span style="color:#228b22"></span>		transform.position = Vector3.Lerp(transform.position, m_TargetPosition, Time.deltaTime * smooth);
		
		<span style="color:#228b22">// 望向指定位置
</span><span style="color:#228b22"></span>		transform.LookAt(follow);
	}
}
</code></pre></div><h4 id="522-firstcontroller-主控制器">5.2.2 FirstController 主控制器</h4>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">FirstController</span> : MonoBehaviour, IUserAction, ISceneController
{
    <span style="color:#8b008b;font-weight:bold">public</span> GemFactory gemFactory;
    <span style="color:#8b008b;font-weight:bold">public</span> GhostFactory ghostFactory;
    <span style="color:#8b008b;font-weight:bold">public</span> PlayerFactory playerFactory;
    <span style="color:#8b008b;font-weight:bold">public</span> GhostActionManager ghostActionManager;
    <span style="color:#8b008b;font-weight:bold">public</span> GameEventManager gameEventManager;
    <span style="color:#8b008b;font-weight:bold">public</span> RoomSensor roomSensor;
    <span style="color:#8b008b;font-weight:bold">public</span> ScoreRecorder scoreRecorder;
    <span style="color:#8b008b;font-weight:bold">public</span> UserGUI userGUI; 

    <span style="color:#8b008b;font-weight:bold">public</span> List&lt;GameObject&gt; gems; 
    <span style="color:#8b008b;font-weight:bold">public</span> List&lt;GameObject&gt; ghosts;
    <span style="color:#8b008b;font-weight:bold">public</span> GameObject player;

    <span style="color:#00688b;font-weight:bold">int</span> restGemNum;
    <span style="color:#00688b;font-weight:bold">int</span> gameState;
    <span style="color:#00688b;font-weight:bold">int</span> countTime;
    <span style="color:#00688b;font-weight:bold">int</span> second;

    <span style="color:#8b008b;font-weight:bold">void</span> Start(){
        
        <span style="color:#228b22">// 设置FPS，用于确保秒数计时准确
</span><span style="color:#228b22"></span>        Application.targetFrameRate = <span style="color:#b452cd">60</span>;
        countTime = <span style="color:#b452cd">0</span>;
        second = <span style="color:#b452cd">0</span>;

        SSDirector.GetInstance().CurrentSceneController = <span style="color:#8b008b;font-weight:bold">this</span>;

        gameObject.AddComponent&lt;GemFactory&gt;();
        gemFactory = Singleton&lt;GemFactory&gt;.Instance;

        gameObject.AddComponent&lt;GhostFactory&gt;();
        ghostFactory = Singleton&lt;GhostFactory&gt;.Instance;

        gameObject.AddComponent&lt;PlayerFactory&gt;();
        playerFactory = Singleton&lt;PlayerFactory&gt;.Instance;

        LoadResources();

        gameObject.AddComponent&lt;GhostActionManager&gt;();
        ghostActionManager = Singleton&lt;GhostActionManager&gt;.Instance;

        <span style="color:#8b008b;font-weight:bold">for</span>(<span style="color:#00688b;font-weight:bold">int</span> i = <span style="color:#b452cd">0</span>; i &lt; ghosts.Count; i++) {
            ghostActionManager.Walk(player, ghosts[i]);
        }

        gameObject.AddComponent&lt;GameEventManager&gt;();
        gameEventManager = Singleton&lt;GameEventManager&gt;.Instance;

        gameObject.AddComponent&lt;RoomSensor&gt;();
        roomSensor = Singleton&lt;RoomSensor&gt;.Instance;

        gameObject.AddComponent&lt;ScoreRecorder&gt;();
        scoreRecorder = Singleton&lt;ScoreRecorder&gt;.Instance;

        gameObject.AddComponent&lt;UserGUI&gt;();
        userGUI = Singleton&lt;UserGUI&gt;.Instance;

        Camera.main.GetComponent&lt;CameraController&gt;().player = player;

        restGemNum = gems.Count;
    }

    <span style="color:#8b008b;font-weight:bold">void</span> Update() {
        <span style="color:#228b22">// 计时
</span><span style="color:#228b22"></span>        <span style="color:#8b008b;font-weight:bold">if</span>(userGUI.start == <span style="color:#8b008b;font-weight:bold">true</span> &amp;&amp; userGUI.gameover != <span style="color:#8b008b;font-weight:bold">true</span> &amp;&amp; userGUI.win != <span style="color:#8b008b;font-weight:bold">true</span>){
            countTime += <span style="color:#b452cd">1</span>;
            <span style="color:#8b008b;font-weight:bold">if</span>(countTime == <span style="color:#b452cd">60</span>){
                countTime = <span style="color:#b452cd">0</span>;
                second++;   
            }
        } 
        
        <span style="color:#228b22">// UI更新
</span><span style="color:#228b22"></span>        userGUI.UpdateScoreText(scoreRecorder.score, restGemNum, second);
    }

    <span style="color:#8b008b;font-weight:bold">void</span> OnEnable() {
        GameEventManager.OnGoalLost += OnGoalLost;
        GameEventManager.OnFollowing += OnFollowing;
        GameEventManager.GameOver += GameOver;
        GameEventManager.Win += Win;
        GameEventManager.OnGettingGem += OnGettingGem;
    }

    <span style="color:#8b008b;font-weight:bold">void</span> OnDisable() {
        GameEventManager.OnGoalLost -= OnGoalLost;
        GameEventManager.OnFollowing -= OnFollowing;
        GameEventManager.GameOver -= GameOver;
        GameEventManager.Win -= Win;
        GameEventManager.OnGettingGem -= OnGettingGem;
    }

    <span style="color:#228b22">// 在地图中随机生成十个宝石，随机分布在16个房间中
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> List&lt;GameObject&gt; generateRandomGems(){
        List&lt;GameObject&gt; gems = <span style="color:#8b008b;font-weight:bold">new</span> List&lt;GameObject&gt;();

        <span style="color:#00688b;font-weight:bold">int</span>[] validRoom = <span style="color:#8b008b;font-weight:bold">new</span> <span style="color:#00688b;font-weight:bold">int</span>[<span style="color:#b452cd">13</span>] {<span style="color:#b452cd">1</span>,<span style="color:#b452cd">2</span>,<span style="color:#b452cd">3</span>,<span style="color:#b452cd">4</span>,<span style="color:#b452cd">5</span>,<span style="color:#b452cd">6</span>,<span style="color:#b452cd">8</span>,<span style="color:#b452cd">9</span>,<span style="color:#b452cd">10</span>,<span style="color:#b452cd">11</span>,<span style="color:#b452cd">12</span>,<span style="color:#b452cd">13</span>,<span style="color:#b452cd">16</span>};
        <span style="color:#00688b;font-weight:bold">int</span>[] gemRoom = <span style="color:#8b008b;font-weight:bold">new</span> <span style="color:#00688b;font-weight:bold">int</span>[<span style="color:#b452cd">10</span>];
        <span style="color:#00688b;font-weight:bold">int</span>[] notChoose = <span style="color:#8b008b;font-weight:bold">new</span> <span style="color:#00688b;font-weight:bold">int</span>[<span style="color:#b452cd">3</span>];

        <span style="color:#228b22">// 一共要生成10个宝石，设定上有13个房间（validRoom）可以放宝石
</span><span style="color:#228b22"></span>        <span style="color:#228b22">// 随机选取其中3个房间（notChoose）不放宝石，剩下的10个房间（gemRoom）放宝石
</span><span style="color:#228b22"></span>        notChoose[<span style="color:#b452cd">0</span>] = Random.Range(<span style="color:#b452cd">0</span>, validRoom.Length);
        <span style="color:#8b008b;font-weight:bold">do</span>{
            notChoose[<span style="color:#b452cd">1</span>] = Random.Range(<span style="color:#b452cd">0</span>, validRoom.Length);
        }<span style="color:#8b008b;font-weight:bold">while</span> (notChoose[<span style="color:#b452cd">0</span>] == notChoose[<span style="color:#b452cd">1</span>]);
        <span style="color:#8b008b;font-weight:bold">do</span>{
            notChoose[<span style="color:#b452cd">2</span>] = Random.Range(<span style="color:#b452cd">0</span>, validRoom.Length);
        }<span style="color:#8b008b;font-weight:bold">while</span> (notChoose[<span style="color:#b452cd">0</span>] == notChoose[<span style="color:#b452cd">2</span>] || notChoose[<span style="color:#b452cd">1</span>] == notChoose[<span style="color:#b452cd">2</span>]);

        <span style="color:#00688b;font-weight:bold">int</span> j = <span style="color:#b452cd">0</span>;
        <span style="color:#8b008b;font-weight:bold">for</span>(<span style="color:#00688b;font-weight:bold">int</span> i = <span style="color:#b452cd">0</span>; i &lt; validRoom.Length; i++){
            <span style="color:#8b008b;font-weight:bold">if</span>(i != notChoose[<span style="color:#b452cd">0</span>] &amp;&amp; i != notChoose[<span style="color:#b452cd">1</span>] &amp;&amp; i != notChoose[<span style="color:#b452cd">2</span>]){
                <span style="color:#00688b;font-weight:bold">int</span> t = validRoom[i];
                gemRoom[j] = t;
                j++;
            }
        }

        <span style="color:#228b22">// 调用宝石工厂创建10个宝石
</span><span style="color:#228b22"></span>        <span style="color:#8b008b;font-weight:bold">for</span>(<span style="color:#00688b;font-weight:bold">int</span> i = <span style="color:#b452cd">0</span>; i &lt; <span style="color:#b452cd">10</span>; i++){
            GameObject gem = gemFactory.GenGem(i + <span style="color:#b452cd">1</span>, gemRoom[i]);
            gems.Add(gem);
        }

        <span style="color:#8b008b;font-weight:bold">return</span> gems;
    }

    <span style="color:#228b22">// 在地图中生成若干幽灵，幽灵的个数和位置可以在此函数调整
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> List&lt;GameObject&gt; generateRandomGhosts(){
        
        List&lt;GameObject&gt; ghosts = <span style="color:#8b008b;font-weight:bold">new</span> List&lt;GameObject&gt;();
    
        <span style="color:#8b008b;font-weight:bold">for</span> (<span style="color:#00688b;font-weight:bold">int</span> i = <span style="color:#b452cd">0</span>; i &lt; <span style="color:#b452cd">4</span>; i++) {
            <span style="color:#8b008b;font-weight:bold">for</span> (<span style="color:#00688b;font-weight:bold">int</span> j = <span style="color:#b452cd">0</span>; j &lt; <span style="color:#b452cd">4</span>; j++) {
                <span style="color:#8b008b;font-weight:bold">if</span>(i == <span style="color:#b452cd">2</span> &amp;&amp; j == <span style="color:#b452cd">1</span>){
                    <span style="color:#228b22">// 起始房间：不生成灵魂
</span><span style="color:#228b22"></span>                }
                <span style="color:#8b008b;font-weight:bold">else</span> <span style="color:#8b008b;font-weight:bold">if</span>(i == <span style="color:#b452cd">0</span> &amp;&amp; j == <span style="color:#b452cd">1</span>){
                    <span style="color:#228b22">// 邪恶房间：5只幽灵
</span><span style="color:#228b22"></span>                    GameObject ghost = ghostFactory.GenGhost(i, j, <span style="color:#b452cd">2</span>, <span style="color:#b452cd">2</span>);
                    ghosts.Add(ghost);

                    GameObject ghost2 = ghostFactory.GenGhost(i, j, -<span style="color:#b452cd">3</span>, -<span style="color:#b452cd">2</span>);
                    ghosts.Add(ghost2);

                    GameObject ghost3 = ghostFactory.GenGhost(i, j, <span style="color:#b452cd">1</span>, -<span style="color:#b452cd">3</span>);
                    ghosts.Add(ghost3);

                    GameObject ghost4 = ghostFactory.GenGhost(i, j, -<span style="color:#b452cd">1</span>, <span style="color:#b452cd">2</span>);
                    ghosts.Add(ghost4);

                    GameObject ghost5 = ghostFactory.GenGhost(i, j, -<span style="color:#b452cd">2</span>, <span style="color:#b452cd">1</span>);
                    ghosts.Add(ghost5);
                }
                <span style="color:#8b008b;font-weight:bold">else</span>{
                    <span style="color:#228b22">// 普通房间：3只幽灵
</span><span style="color:#228b22"></span>                    GameObject ghost = ghostFactory.GenGhost(i, j, <span style="color:#b452cd">2</span>, <span style="color:#b452cd">2</span>);
                    ghosts.Add(ghost);

                    GameObject ghost2 = ghostFactory.GenGhost(i, j, -<span style="color:#b452cd">3</span>, -<span style="color:#b452cd">2</span>);
                    ghosts.Add(ghost2);

                    GameObject ghost3 = ghostFactory.GenGhost(i, j, <span style="color:#b452cd">1</span>, -<span style="color:#b452cd">3</span>);
                    ghosts.Add(ghost3);
                }
            }
        }
        <span style="color:#8b008b;font-weight:bold">return</span> ghosts;
    }
    
    <span style="color:#228b22">// 载入资源：生成玩家、幽灵和宝石
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> LoadResources(){
        Debug.Log(<span style="color:#cd5555">&#34;Load Resource...&#34;</span>);

        gems = generateRandomGems();
        ghosts = generateRandomGhosts();
        player = playerFactory.GenPlayer();
    }

    <span style="color:#228b22">// 使用 Scene Manager 重新载入游戏场景
</span><span style="color:#228b22"></span>    <span style="color:#228b22">// 参考博客：https://www.cnblogs.com/caicaicaicai/p/6475600.html 来解决不渲染光线的问题
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> Restart(){
        SceneManager.LoadScene(<span style="color:#cd5555">&#34;Scenes/Play&#34;</span>);
    }

    <span style="color:#228b22">// 每甩开一个幽灵，玩家得 1 分
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> OnGoalLost(GameObject ghost) {
        ghostActionManager.Walk(player, ghost);
        <span style="color:#8b008b;font-weight:bold">if</span>(player.GetComponent&lt;PlayerData&gt;().alive){
            scoreRecorder.Record(<span style="color:#b452cd">1</span>);
        }
    }

    <span style="color:#228b22">// 玩家进入幽灵的视野，幽灵开始追击
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> OnFollowing(GameObject ghost) {
        <span style="color:#8b008b;font-weight:bold">if</span>(player.GetComponent&lt;PlayerData&gt;().alive) {
            ghostActionManager.Follow(player, ghost);
            Debug.Log(<span style="color:#cd5555">&#34;I See U!&#34;</span>);
        }
    }

    <span style="color:#228b22">// 玩家获取水晶，当获得全部水晶，游戏获胜
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> OnGettingGem(GameObject gem) {
        gem.SetActive(<span style="color:#8b008b;font-weight:bold">false</span>);
        restGemNum--;
        <span style="color:#8b008b;font-weight:bold">if</span>(restGemNum == <span style="color:#b452cd">0</span>) {
            Win();
        }
    }

    <span style="color:#228b22">// 失败
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> GameOver() {
        Debug.Log(<span style="color:#cd5555">&#34;GameOver&#34;</span>);
        player.GetComponent&lt;PlayerData&gt;().alive = <span style="color:#8b008b;font-weight:bold">false</span>;
        player.SetActive(<span style="color:#8b008b;font-weight:bold">false</span>);
        userGUI.gameover = <span style="color:#8b008b;font-weight:bold">true</span>;
    }

    <span style="color:#228b22">// 胜利，幽灵消失，你可以在房间里闲逛
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> Win() {
        Debug.Log(<span style="color:#cd5555">&#34;YouWin&#34;</span>);
        <span style="color:#8b008b;font-weight:bold">for</span>(<span style="color:#00688b;font-weight:bold">int</span> i = <span style="color:#b452cd">0</span>; i &lt; ghosts.Count; i++){
            ghosts[i].SetActive(<span style="color:#8b008b;font-weight:bold">false</span>);
        }
        userGUI.win = <span style="color:#8b008b;font-weight:bold">true</span>;
    }
}
</code></pre></div><h4 id="523-gameeventmanager-事件管理器">5.2.3 GameEventManager 事件管理器</h4>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#8b008b;font-weight:bold">using</span> <span style="color:#008b45;text-decoration:underline">System.Collections</span>;
<span style="color:#8b008b;font-weight:bold">using</span> <span style="color:#008b45;text-decoration:underline">System.Collections.Generic</span>;
<span style="color:#8b008b;font-weight:bold">using</span> <span style="color:#008b45;text-decoration:underline">UnityEngine</span>;

<span style="color:#228b22">// 管理游戏时间，参考：https://gitee.com/Enrique518/unity3d_hw/tree/master/Intelligent%20Patrol/Assets/Scripts
</span><span style="color:#228b22"></span><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">GameEventManager</span> : MonoBehaviour
{
    <span style="color:#228b22">// 玩家逃脱事件
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">delegate</span> <span style="color:#8b008b;font-weight:bold">void</span> EscapeEvent(GameObject ghost);
    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">static</span> <span style="color:#8b008b;font-weight:bold">event</span> EscapeEvent OnGoalLost;
    <span style="color:#228b22">// 巡逻兵追击事件
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">delegate</span> <span style="color:#8b008b;font-weight:bold">void</span> FollowEvent(GameObject ghost);
    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">static</span> <span style="color:#8b008b;font-weight:bold">event</span> FollowEvent OnFollowing;
    <span style="color:#228b22">// 游戏失败事件
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">delegate</span> <span style="color:#8b008b;font-weight:bold">void</span> GameOverEvent();
    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">static</span> <span style="color:#8b008b;font-weight:bold">event</span> GameOverEvent GameOver;
    <span style="color:#228b22">// 游戏胜利事件
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">delegate</span> <span style="color:#8b008b;font-weight:bold">void</span> WinEvent();
    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">static</span> <span style="color:#8b008b;font-weight:bold">event</span> WinEvent Win;

    <span style="color:#228b22">// 获取宝石事件
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">delegate</span> <span style="color:#8b008b;font-weight:bold">void</span> GemEvent(GameObject gem);
    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">static</span> <span style="color:#8b008b;font-weight:bold">event</span> GemEvent OnGettingGem;

    <span style="color:#228b22">// 玩家逃脱
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> PlayerEscape(GameObject ghost) {
        <span style="color:#8b008b;font-weight:bold">if</span> (OnGoalLost != <span style="color:#8b008b;font-weight:bold">null</span>) {
            OnGoalLost(ghost);
        }
    }

    <span style="color:#228b22">// 获得水晶
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> GettingGem(GameObject gem) {
        <span style="color:#8b008b;font-weight:bold">if</span>(OnGettingGem != <span style="color:#8b008b;font-weight:bold">null</span>){
            OnGettingGem(gem);
        }
    }

    <span style="color:#228b22">// 幽灵追击
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> FollowPlayer(GameObject ghost) {
        <span style="color:#8b008b;font-weight:bold">if</span> (OnFollowing != <span style="color:#8b008b;font-weight:bold">null</span>) {
            OnFollowing(ghost);
        }
    }

    <span style="color:#228b22">// 玩家被抓
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> OnPlayerCatched() {
        <span style="color:#8b008b;font-weight:bold">if</span> (GameOver != <span style="color:#8b008b;font-weight:bold">null</span>) {
            GameOver();
        }
    }
}
</code></pre></div><h4 id="524-playercontroller-玩家运动控制器">5.2.4 PlayerController 玩家运动控制器</h4>
<ul>
<li><code>WalkBool</code>是一个用于控制玩家进入前进还是滞留的Bool型变量，它在玩家的Animator中给出。</li>
</ul>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#8b008b;font-weight:bold">using</span> <span style="color:#008b45;text-decoration:underline">System.Collections</span>;
<span style="color:#8b008b;font-weight:bold">using</span> <span style="color:#008b45;text-decoration:underline">System.Collections.Generic</span>;
<span style="color:#8b008b;font-weight:bold">using</span> <span style="color:#008b45;text-decoration:underline">UnityEngine</span>;

<span style="color:#228b22">// 玩家控制器：指定玩家操作
</span><span style="color:#228b22"></span><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">PlayerController</span> : MonoBehaviour
{
    <span style="color:#8b008b;font-weight:bold">private</span> Animator ani;

    <span style="color:#8b008b;font-weight:bold">void</span> Start()
    {
        ani = GetComponent&lt;Animator&gt;();
    }

    <span style="color:#228b22">// W键前进，Q E A D S 转向，空格键停下
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">void</span> Update()
    {
        <span style="color:#8b008b;font-weight:bold">if</span>(Input.GetKeyDown(<span style="color:#cd5555">&#34;w&#34;</span>)){
            ani.SetBool(<span style="color:#cd5555">&#34;WalkBool&#34;</span>, <span style="color:#8b008b;font-weight:bold">true</span>);
        }
        <span style="color:#8b008b;font-weight:bold">else</span> <span style="color:#8b008b;font-weight:bold">if</span>(Input.GetKeyDown(<span style="color:#cd5555">&#34;q&#34;</span>)){
            transform.Rotate(<span style="color:#b452cd">0</span>, -<span style="color:#b452cd">45F</span>, <span style="color:#b452cd">0</span>);
        }
        <span style="color:#8b008b;font-weight:bold">else</span> <span style="color:#8b008b;font-weight:bold">if</span>(Input.GetKeyDown(<span style="color:#cd5555">&#34;e&#34;</span>)){
            transform.Rotate(<span style="color:#b452cd">0</span>, <span style="color:#b452cd">45F</span>, <span style="color:#b452cd">0</span>);
        }
        <span style="color:#8b008b;font-weight:bold">else</span> <span style="color:#8b008b;font-weight:bold">if</span>(Input.GetKeyDown(<span style="color:#cd5555">&#34;a&#34;</span>)){
            transform.Rotate(<span style="color:#b452cd">0</span>, -<span style="color:#b452cd">90F</span>, <span style="color:#b452cd">0</span>);
        }
        <span style="color:#8b008b;font-weight:bold">else</span> <span style="color:#8b008b;font-weight:bold">if</span>(Input.GetKeyDown(<span style="color:#cd5555">&#34;s&#34;</span>)){
            transform.Rotate(<span style="color:#b452cd">0</span>, <span style="color:#b452cd">180F</span>, <span style="color:#b452cd">0</span>);
        }
        <span style="color:#8b008b;font-weight:bold">else</span> <span style="color:#8b008b;font-weight:bold">if</span>(Input.GetKeyDown(<span style="color:#cd5555">&#34;d&#34;</span>)){
            transform.Rotate(<span style="color:#b452cd">0</span>, <span style="color:#b452cd">90F</span>, <span style="color:#b452cd">0</span>);
        }

        <span style="color:#8b008b;font-weight:bold">if</span>(Input.GetKey(KeyCode.Space)){
            ani.SetBool(<span style="color:#cd5555">&#34;WalkBool&#34;</span>, <span style="color:#8b008b;font-weight:bold">false</span>);
        }
    }
}
</code></pre></div><h4 id="525-scorerecorder-计分板">5.2.5 ScoreRecorder 计分板</h4>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#228b22">// 计分器：每幽摆脱一个幽灵的追击，获得 1 分
</span><span style="color:#228b22"></span><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">ScoreRecorder</span> : MonoBehaviour
{
    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#00688b;font-weight:bold">int</span> score;

    <span style="color:#8b008b;font-weight:bold">void</span> Start() {
        score = <span style="color:#b452cd">0</span>;
    }

    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> Record(<span style="color:#00688b;font-weight:bold">int</span> i) {
        score += i;
    }

    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> Reset() {
        score = <span style="color:#b452cd">0</span>;
    }
}
</code></pre></div><h3 id="53-sensor模块">5.3 Sensor模块</h3>
<p><strong>位于：MyScripts/Sensor，用于处理不同对象的碰撞 / 接触。</strong></p>
<h4 id="531-gemcollidesensor-宝石碰撞检测器">5.3.1 GemCollideSensor 宝石碰撞检测器</h4>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#228b22">// 宝石碰撞检测器：被挂载在宝石上，用于检测玩家是否接触到宝石
</span><span style="color:#228b22"></span><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">GemCollideSensor</span> : MonoBehaviour
{
    FirstController sceneController;      <span style="color:#228b22">// 当前的场记
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">void</span> OnTriggerEnter(Collider collider) {
        sceneController = SSDirector.GetInstance().CurrentSceneController <span style="color:#8b008b;font-weight:bold">as</span> FirstController;
        <span style="color:#8b008b;font-weight:bold">if</span> (collider.gameObject.Equals(sceneController.player)) {
            <span style="color:#228b22">// 玩家获取宝石
</span><span style="color:#228b22"></span>            Singleton&lt;GameEventManager&gt;.Instance.GettingGem(<span style="color:#8b008b;font-weight:bold">this</span>.gameObject);
        }
    }
}
</code></pre></div><h4 id="532-ghostcollidesensor-幽灵碰撞检测器">5.3.2 GhostCollideSensor 幽灵碰撞检测器</h4>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-C#" data-lang="C#"><span style="color:#228b22">// 幽灵碰撞检测器：用于检测玩家是否与幽灵发生了接触
</span><span style="color:#228b22"></span><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">GhostCollideSensor</span> : MonoBehaviour
{
    FirstController sceneController;
    <span style="color:#8b008b;font-weight:bold">void</span> OnTriggerEnter(Collider collider) {
        sceneController = SSDirector.GetInstance().CurrentSceneController <span style="color:#8b008b;font-weight:bold">as</span> FirstController;
        <span style="color:#8b008b;font-weight:bold">if</span> (collider.gameObject.Equals(sceneController.player)) {
            <span style="color:#228b22">// 幽灵抓到玩家
</span><span style="color:#228b22"></span>            Debug.Log(<span style="color:#cd5555">&#34;Ghost: Catch U!&#34;</span>);
            Singleton&lt;GameEventManager&gt;.Instance.OnPlayerCatched();
        }
        <span style="color:#8b008b;font-weight:bold">else</span> {
            <span style="color:#228b22">// 幽灵碰到障碍物
</span><span style="color:#228b22"></span>            Debug.Log(<span style="color:#cd5555">&#34;Ghost: Oops!&#34;</span>);
            <span style="color:#8b008b;font-weight:bold">this</span>.GetComponent&lt;GhostData&gt;().isCollided = <span style="color:#8b008b;font-weight:bold">true</span>;
        }
    }
}
</code></pre></div><h4 id="533-inrangesensor-幽灵范围感知器">5.3.3 InRangeSensor 幽灵范围感知器</h4>
<ul>
<li>
<p>幽灵的范围感知空间不是幽灵对象本身而是幽灵的一个半径为5的不可见的球形子对象，这样做的原因是幽灵本身还要作为与玩家碰撞的触发器，将二者区分开能够简化编程。</p>
</li>
<li>
<p>可以通过 <code>transform.GetChild(index)</code> 来获得子对象，<code>index</code>表示第几个子对象（从 0 开始）</p>
</li>
</ul>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#228b22">// 幽灵范围感知器：挂载在幽灵的第一个子对象上（一个可穿透的半径为 5 的不可见球）
</span><span style="color:#228b22">// 用于检测玩家是否位于幽灵的感知范围内
</span><span style="color:#228b22"></span><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">InRangeSensor</span> : MonoBehaviour
{
    FirstController sceneController;
    <span style="color:#8b008b;font-weight:bold">public</span> GameObject ghost;

    <span style="color:#8b008b;font-weight:bold">void</span> OnTriggerEnter(Collider collider) {
        sceneController = SSDirector.GetInstance().CurrentSceneController <span style="color:#8b008b;font-weight:bold">as</span> FirstController;
        <span style="color:#8b008b;font-weight:bold">if</span> (collider.gameObject.Equals(sceneController.player)) {
            ghost.GetComponent&lt;GhostData&gt;().isInRange = <span style="color:#8b008b;font-weight:bold">true</span>;
        }
    }
    <span style="color:#8b008b;font-weight:bold">void</span> OnTriggerExit(Collider collider) {
        sceneController = SSDirector.GetInstance().CurrentSceneController <span style="color:#8b008b;font-weight:bold">as</span> FirstController;
        <span style="color:#8b008b;font-weight:bold">if</span> (collider.gameObject.Equals(sceneController.player)) {
            <span style="color:#228b22">// 玩家离开幽灵视线
</span><span style="color:#228b22"></span>            ghost.GetComponent&lt;GhostData&gt;().isInRange = <span style="color:#8b008b;font-weight:bold">false</span>;
        }
    }
}
</code></pre></div><h4 id="534-roomsensor-房间检测器">5.3.4 RoomSensor 房间检测器</h4>
<ul>
<li>
<p>由于房间是我们假想的而并非一个游戏对象，故在检测玩家/幽灵所在房间时，需要通过其位置计算出房间号，而非通过控制器。</p>
</li>
<li>
<p>在检测幽灵碰撞时，我降低了幽灵检测的频率，通过这种方法来避免幽灵卡死。出现卡死的原因是幽灵跨越边界时检测到，但还没等幽灵移回合法区域，再次检测到幽灵跨越边界，导致幽灵卡死在一个位置并不断转向。</p>
</li>
</ul>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-C#" data-lang="C#"><span style="color:#228b22">// 房间检测器：更新玩家所在的房间，约束幽灵不能离开自己所属房间
</span><span style="color:#228b22"></span><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">RoomSensor</span> : MonoBehaviour
{
    FirstController sceneController;

    <span style="color:#00688b;font-weight:bold">float</span>[] ghostX = <span style="color:#8b008b;font-weight:bold">new</span> <span style="color:#00688b;font-weight:bold">float</span>[<span style="color:#b452cd">4</span>] {-<span style="color:#b452cd">15F</span>, -<span style="color:#b452cd">5F</span>, <span style="color:#b452cd">5F</span>, <span style="color:#b452cd">15F</span>};
    <span style="color:#00688b;font-weight:bold">float</span>[] ghostZ = <span style="color:#8b008b;font-weight:bold">new</span> <span style="color:#00688b;font-weight:bold">float</span>[<span style="color:#b452cd">4</span>] {<span style="color:#b452cd">15F</span>, <span style="color:#b452cd">5F</span>, -<span style="color:#b452cd">5F</span>, -<span style="color:#b452cd">15F</span>};
    <span style="color:#00688b;font-weight:bold">float</span> range = <span style="color:#b452cd">4F</span>;                   <span style="color:#228b22">// 幽灵移动范围（正方形）的边长
</span><span style="color:#228b22"></span>
    <span style="color:#00688b;font-weight:bold">int</span> tri = <span style="color:#b452cd">0</span>;
    <span style="color:#8b008b;font-weight:bold">void</span> Update() {
        sceneController = SSDirector.GetInstance().CurrentSceneController <span style="color:#8b008b;font-weight:bold">as</span> FirstController;
        
        <span style="color:#228b22">// 更新玩家所在的房间号
</span><span style="color:#228b22"></span>        PlayerUpdate();

        tri++;
        <span style="color:#228b22">// 降低幽灵的检查频率，避免反复转弯
</span><span style="color:#228b22"></span>        <span style="color:#8b008b;font-weight:bold">if</span>(tri == <span style="color:#b452cd">20</span>){
            GhostCheck();
            tri = <span style="color:#b452cd">0</span>;
        }  
    }

    <span style="color:#8b008b;font-weight:bold">void</span> PlayerUpdate() {
        GameObject player = sceneController.player;
        Vector3 position = player.transform.position;
        <span style="color:#00688b;font-weight:bold">float</span> x = position.x;
        <span style="color:#00688b;font-weight:bold">float</span> z = position.z;
        <span style="color:#00688b;font-weight:bold">int</span> row = (<span style="color:#00688b;font-weight:bold">int</span>) ((x + <span style="color:#b452cd">20</span>) / <span style="color:#b452cd">10</span> + <span style="color:#b452cd">1</span>);
        <span style="color:#00688b;font-weight:bold">int</span> col = (<span style="color:#00688b;font-weight:bold">int</span>) (<span style="color:#b452cd">4</span> - (z + <span style="color:#b452cd">20</span>) / <span style="color:#b452cd">10</span>);
        player.GetComponent&lt;PlayerData&gt;().playerRoomID = row + col * <span style="color:#b452cd">4</span>;
    }

    <span style="color:#8b008b;font-weight:bold">void</span> GhostCheck() {
        <span style="color:#8b008b;font-weight:bold">for</span>(<span style="color:#00688b;font-weight:bold">int</span> i = <span style="color:#b452cd">0</span> ; i &lt; sceneController.ghosts.Count ; i++) {
            GameObject ghost = sceneController.ghosts[i];
            Vector3 gPosition = ghost.transform.position;
            
            <span style="color:#228b22">// 幽灵的位置
</span><span style="color:#228b22"></span>            <span style="color:#00688b;font-weight:bold">float</span> gX = gPosition.x;
            <span style="color:#00688b;font-weight:bold">float</span> gZ = gPosition.z;

            <span style="color:#228b22">// 房间对应的行数和列数
</span><span style="color:#228b22"></span>            <span style="color:#00688b;font-weight:bold">int</span> gRoomID = ghost.GetComponent&lt;GhostData&gt;().ghostRoomID;
            <span style="color:#00688b;font-weight:bold">int</span> gRow = (gRoomID - <span style="color:#b452cd">1</span>) / <span style="color:#b452cd">4</span>;
            <span style="color:#00688b;font-weight:bold">int</span> gCol = (gRoomID - <span style="color:#b452cd">1</span>) % <span style="color:#b452cd">4</span>;
            
            <span style="color:#228b22">// 房间中心的位置
</span><span style="color:#228b22"></span>            <span style="color:#00688b;font-weight:bold">float</span> cX = ghostX[gCol];
            <span style="color:#00688b;font-weight:bold">float</span> cZ = ghostZ[gRow];
            
            <span style="color:#8b008b;font-weight:bold">if</span>(gX &lt; cX - range || gX &gt; cX + range || gZ &lt; cZ - range || gZ &gt; cZ + range){
                <span style="color:#228b22">// 如果幽灵尝试离开房间，则视为发生碰撞
</span><span style="color:#228b22"></span>                ghost.GetComponent&lt;GhostData&gt;().isCollided = <span style="color:#8b008b;font-weight:bold">true</span>;
            }
        }
    }
}
</code></pre></div><h3 id="54-action模块">5.4 Action模块</h3>
<p><strong>位于：MyScripts/Action/GhostAction，用于处理幽灵的运动和状态切换。</strong></p>
<h4 id="541-ghostwalkaction-幽灵动作---闲逛">5.4.1 GhostWalkAction 幽灵动作 - 闲逛</h4>
<ul>
<li>在幽灵闲逛的过程中，要随时检测是否进入追击状态，然后切换为追击模式。</li>
</ul>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">GhostWalkAction</span> : SSAction
{
    <span style="color:#8b008b;font-weight:bold">private</span> <span style="color:#00688b;font-weight:bold">float</span> speed = <span style="color:#b452cd">0.5F</span>;     <span style="color:#228b22">// 闲逛速度
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">private</span> <span style="color:#00688b;font-weight:bold">float</span> GhostX, GhostZ;   <span style="color:#228b22">// 幽灵位置                 
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">private</span> <span style="color:#00688b;font-weight:bold">bool</span> turn = <span style="color:#8b008b;font-weight:bold">true</span>;       <span style="color:#228b22">// 转向信号
</span><span style="color:#228b22"></span>
    <span style="color:#8b008b;font-weight:bold">public</span> GameObject player;       <span style="color:#228b22">// 玩家对象
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> GameObject ghost;        <span style="color:#228b22">// 幽灵对象
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">private</span> GhostData gData;        <span style="color:#228b22">// 幽灵数据
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">private</span> PlayerData pData;       <span style="color:#228b22">// 玩家数据
</span><span style="color:#228b22"></span>
    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">static</span> GhostWalkAction GetAction(GameObject player, GameObject ghost) {
        GhostWalkAction action = CreateInstance&lt;GhostWalkAction&gt;();
        action.GhostX = ghost.transform.position.x;
        action.GhostZ = ghost.transform.position.z;
        action.player = player;
        action.ghost = ghost;
        <span style="color:#8b008b;font-weight:bold">return</span> action;
    }

    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">override</span> <span style="color:#8b008b;font-weight:bold">void</span> Start() {
        gData = ghost.GetComponent&lt;GhostData&gt;();
        pData = player.GetComponent&lt;PlayerData&gt;();
    }

    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">override</span> <span style="color:#8b008b;font-weight:bold">void</span> Update() {

        <span style="color:#8b008b;font-weight:bold">if</span> (!gData.isFollow &amp;&amp; gData.isInRange &amp;&amp; gData.ghostRoomID == pData.playerRoomID &amp;&amp; !gData.isCollided &amp;&amp; pData.alive == <span style="color:#8b008b;font-weight:bold">true</span>) {
            <span style="color:#228b22">// 尾随
</span><span style="color:#228b22"></span>            <span style="color:#8b008b;font-weight:bold">this</span>.destroy = <span style="color:#8b008b;font-weight:bold">true</span>;
            <span style="color:#8b008b;font-weight:bold">this</span>.enable = <span style="color:#8b008b;font-weight:bold">false</span>;
            <span style="color:#8b008b;font-weight:bold">this</span>.callback.SSActionEvent(<span style="color:#8b008b;font-weight:bold">this</span>);
            <span style="color:#8b008b;font-weight:bold">this</span>.gameObject.GetComponent&lt;GhostData&gt;().isFollow = <span style="color:#8b008b;font-weight:bold">true</span>;
            Singleton&lt;GameEventManager&gt;.Instance.FollowPlayer(<span style="color:#8b008b;font-weight:bold">this</span>.gameObject);
        }
        <span style="color:#8b008b;font-weight:bold">else</span> {
            <span style="color:#228b22">// 闲逛
</span><span style="color:#228b22"></span>            Walking();
        }
    }

    <span style="color:#8b008b;font-weight:bold">void</span> Walking() {

        <span style="color:#228b22">// 随机转向
</span><span style="color:#228b22"></span>        <span style="color:#8b008b;font-weight:bold">if</span> (turn) {
            GhostX = <span style="color:#8b008b;font-weight:bold">this</span>.transform.position.x + Random.Range(-<span style="color:#b452cd">3F</span>, <span style="color:#b452cd">3F</span>);
            GhostZ = <span style="color:#8b008b;font-weight:bold">this</span>.transform.position.z + Random.Range(-<span style="color:#b452cd">3F</span>, <span style="color:#b452cd">3F</span>);
            <span style="color:#8b008b;font-weight:bold">this</span>.transform.LookAt(<span style="color:#8b008b;font-weight:bold">new</span> Vector3(GhostX, <span style="color:#b452cd">0</span>, GhostZ));
            <span style="color:#8b008b;font-weight:bold">this</span>.gameObject.GetComponent&lt;GhostData&gt;().isCollided = <span style="color:#8b008b;font-weight:bold">false</span>;
            turn = <span style="color:#8b008b;font-weight:bold">false</span>;
        }

        <span style="color:#00688b;font-weight:bold">float</span> distance = Vector3.Distance(transform.position, <span style="color:#8b008b;font-weight:bold">new</span> Vector3(GhostX, <span style="color:#b452cd">0</span>, GhostZ));

        <span style="color:#8b008b;font-weight:bold">if</span> (<span style="color:#8b008b;font-weight:bold">this</span>.gameObject.GetComponent&lt;GhostData&gt;().isCollided) {

            <span style="color:#228b22">// 碰墙时逆时针旋转120~180度
</span><span style="color:#228b22"></span>            <span style="color:#8b008b;font-weight:bold">this</span>.transform.Rotate(Vector3.up, Random.Range(<span style="color:#b452cd">120</span>, <span style="color:#b452cd">180</span>));
            GameObject temp = <span style="color:#8b008b;font-weight:bold">new</span> GameObject();
            temp.transform.position = <span style="color:#8b008b;font-weight:bold">this</span>.transform.position;
            temp.transform.rotation = <span style="color:#8b008b;font-weight:bold">this</span>.transform.rotation;
            temp.transform.Translate(<span style="color:#b452cd">0</span>, <span style="color:#b452cd">0</span>, Random.Range(<span style="color:#b452cd">0.5F</span>, <span style="color:#b452cd">2F</span>));
            GhostX = temp.transform.position.x;
            GhostZ = temp.transform.position.z;
            <span style="color:#8b008b;font-weight:bold">this</span>.transform.LookAt(<span style="color:#8b008b;font-weight:bold">new</span> Vector3(GhostX, <span style="color:#b452cd">0</span>, GhostZ));
            Destroy(temp);
            <span style="color:#8b008b;font-weight:bold">this</span>.gameObject.GetComponent&lt;GhostData&gt;().isCollided = <span style="color:#8b008b;font-weight:bold">false</span>;
            
        } <span style="color:#8b008b;font-weight:bold">else</span> <span style="color:#8b008b;font-weight:bold">if</span> (distance &lt;= <span style="color:#b452cd">0.1F</span>) {
            turn = <span style="color:#8b008b;font-weight:bold">true</span>;
        } <span style="color:#8b008b;font-weight:bold">else</span> {
            <span style="color:#228b22">// 直行
</span><span style="color:#228b22"></span>            <span style="color:#8b008b;font-weight:bold">this</span>.transform.Translate(<span style="color:#b452cd">0</span>, <span style="color:#b452cd">0</span>, speed * Time.deltaTime);
        }
    }
}
</code></pre></div><h4 id="542-ghostfollowaction-幽灵动作---追击">5.4.2 GhostFollowAction 幽灵动作 - 追击</h4>
<ul>
<li>在幽灵追击的过程中，要随时检测是否满足退出条件，然后切换为闲逛模式。</li>
</ul>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#8b008b;font-weight:bold">using</span> <span style="color:#008b45;text-decoration:underline">System.Collections</span>;
<span style="color:#8b008b;font-weight:bold">using</span> <span style="color:#008b45;text-decoration:underline">System.Collections.Generic</span>;
<span style="color:#8b008b;font-weight:bold">using</span> <span style="color:#008b45;text-decoration:underline">UnityEngine</span>;

<span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">GhostFollowAction</span> : SSAction
{
    <span style="color:#8b008b;font-weight:bold">private</span> <span style="color:#00688b;font-weight:bold">float</span> speed = <span style="color:#b452cd">0.8F</span>;     <span style="color:#228b22">// 追击速度（玩家的速度是 1F）
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> GameObject player;       <span style="color:#228b22">// 玩家对象
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> GameObject ghost;        <span style="color:#228b22">// 幽灵对象
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">private</span> GhostData gData;        <span style="color:#228b22">// 幽灵数据
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">private</span> PlayerData pData;       <span style="color:#228b22">// 玩家数据
</span><span style="color:#228b22"></span>
    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">static</span> GhostFollowAction GetAction(GameObject player, GameObject ghost) {
        GhostFollowAction action = CreateInstance&lt;GhostFollowAction&gt;();
        action.player = player;
        action.ghost = ghost;
        <span style="color:#8b008b;font-weight:bold">return</span> action;
    }

    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">override</span> <span style="color:#8b008b;font-weight:bold">void</span> Start() {
        gData = ghost.GetComponent&lt;GhostData&gt;();
        pData = player.GetComponent&lt;PlayerData&gt;();
    }

    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">override</span> <span style="color:#8b008b;font-weight:bold">void</span> Update() {
        
        <span style="color:#8b008b;font-weight:bold">if</span> (gData.isFollow &amp;&amp; (!gData.isInRange || gData.ghostRoomID != pData.playerRoomID || gData.isCollided || pData.alive == <span style="color:#8b008b;font-weight:bold">false</span>)) {
            <span style="color:#228b22">// 放弃跟随
</span><span style="color:#228b22"></span>            <span style="color:#8b008b;font-weight:bold">this</span>.destroy = <span style="color:#8b008b;font-weight:bold">true</span>;
            <span style="color:#8b008b;font-weight:bold">this</span>.enable = <span style="color:#8b008b;font-weight:bold">false</span>;
            <span style="color:#8b008b;font-weight:bold">this</span>.callback.SSActionEvent(<span style="color:#8b008b;font-weight:bold">this</span>);
            <span style="color:#8b008b;font-weight:bold">this</span>.gameObject.GetComponent&lt;GhostData&gt;().isFollow = <span style="color:#8b008b;font-weight:bold">false</span>;
            Singleton&lt;GameEventManager&gt;.Instance.PlayerEscape(<span style="color:#8b008b;font-weight:bold">this</span>.gameObject);
        }
        <span style="color:#8b008b;font-weight:bold">else</span> {
            <span style="color:#228b22">// 尾随
</span><span style="color:#228b22"></span>            Following();
        }
    }

    <span style="color:#8b008b;font-weight:bold">void</span> Following() {
        <span style="color:#228b22">// 面向玩家
</span><span style="color:#228b22"></span>        transform.LookAt(player.transform.position);
        <span style="color:#228b22">// 跟随玩家
</span><span style="color:#228b22"></span>        transform.position = Vector3.MoveTowards(<span style="color:#8b008b;font-weight:bold">this</span>.transform.position, player.transform.position, speed * Time.deltaTime);
    }
}
</code></pre></div><h4 id="543-ghostactionmanager-幽灵动作管理器">5.4.3 GhostActionManager 幽灵动作管理器</h4>
<div class="highlight"><pre style="background-color:#eed;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-c#" data-lang="c#"><span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">class</span> <span style="color:#008b45;font-weight:bold">GhostActionManager</span> : SSActionManager, ISSActionCallback
{
    <span style="color:#8b008b;font-weight:bold">public</span> GhostWalkAction walk;
    <span style="color:#8b008b;font-weight:bold">public</span> GhostFollowAction follow;

    <span style="color:#228b22">// 闲逛
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> Walk(GameObject player, GameObject ghost) {
        <span style="color:#8b008b;font-weight:bold">this</span>.walk = GhostWalkAction.GetAction(player, ghost);
        <span style="color:#8b008b;font-weight:bold">this</span>.RunSSAction(ghost, walk, <span style="color:#8b008b;font-weight:bold">this</span>);
    }

    <span style="color:#228b22">// 追击
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> Follow(GameObject player, GameObject ghost) {
        <span style="color:#8b008b;font-weight:bold">this</span>.follow = GhostFollowAction.GetAction(player, ghost);
        <span style="color:#8b008b;font-weight:bold">this</span>.RunSSAction(ghost, follow, <span style="color:#8b008b;font-weight:bold">this</span>);
    }

    <span style="color:#228b22">// 停止
</span><span style="color:#228b22"></span>    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> DestroyAllActions() {
        DestroyAll();
    }

    <span style="color:#8b008b;font-weight:bold">public</span> <span style="color:#8b008b;font-weight:bold">void</span> SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed, <span style="color:#00688b;font-weight:bold">int</span> intParam = <span style="color:#b452cd">0</span>, <span style="color:#00688b;font-weight:bold">string</span> strParam = <span style="color:#8b008b;font-weight:bold">null</span>, Object objectParam = <span style="color:#8b008b;font-weight:bold">null</span>) {

    }
}
</code></pre></div><h3 id="55-gui模块">5.5 GUI模块</h3>
<p><strong>位于：MyScripts/View，简单界面交互。</strong></p>
<h4 id="551-usergui-界面交互">5.5.1 UserGUI 界面交互</h4>
<ul>
<li>主要组件：
<ul>
<li>右上角刷新按钮：随时重新开始局一新的游戏，</li>
<li>正上方文本框：显示分数和计时。</li>
</ul>
</li>
</ul>
<h2 id="六实现过程中问题">六、实现过程中问题</h2>
<h3 id="61-scenemanager与光线渲染">6.1 SceneManager与光线渲染</h3>
<blockquote>
<p>参考：<a href="https://www.cnblogs.com/caicaicaicai/p/6475600.html">SceneManager.LoadScene调用后新场景会变暗的问题</a></p>
</blockquote>
<ul>
<li>
<p>问题发生在我们调用 SceneManager.LoadScene 获得新场景后，场景的亮度会变暗。</p>
</li>
<li>
<p>对此，我怀疑是Unity3D自动渲染的问题，一种可行的解决步骤如下：</p>
<ul>
<li>
<p>首先，按照如下步骤进入 Lighting：菜单栏 - Window - Rendering - Lighting</p>
</li>
<li>
<p>然后，创建一个新的 Lighting Setting，创建后会进行编译，编译结束后，会在当前文件夹生成该配置，问题解决。</p>
<p><img src="../../../../images/unity3d4s/unity3d4//8.png" alt="../../images/unity3d4-20211129003551712"></p>
</li>
</ul>
</li>
</ul>

                    
                    <HR width="100%" id="EOF">
		    <p style="color:#777;">Last modified on 2021-11-28</p>
                    
                </div>
            </div>
            
            
            <nav class="post-pagination">

                
                <a class="newer-posts">
			Next<br>No newer posts.
                </a>
                
                
                
                <a class="older-posts" href="https://wondrouswisdomcard.gitee.io/posts/unity3d3/">
			Previous<br>用Unity3D实现打飞碟游戏
                </a>
                
            </nav>
            <div class="post-comment-wrapper">
                
<div class="comments">
    <vue-disqus shortname="Your disqus short name."></vue-disqus>
</div>
<noscript><noscript>Please activate JavaScript to view comments.</noscript></noscript>
<a href="https://disqus.com/" class="dsq-brlink">Comments powered by <span class="logo-disqus">Disqus</span></a>



<div id="gitalk-container"></div>






            </div>
        </div>
    </div>
</div>

            </div><div id="single-column-footer">
Hugo Theme <a href="https://github.com/amazingrise/hugo-theme-diary">Diary</a> by <a href="https://amazingrise.net">Rise</a>
<br>
Ported from <a href="https://mak1t0.cc/" target="_blank" rel="noreferrer noopener">Makito</a>'s <a href="https://github.com/SumiMakito/hexo-theme-journal/" target="_blank" rel="noreferrer noopener">Journal.</a> <br>
<br>

&copy;
	
	This is a customized copyright.
	
</div>
            </div>
    <script>
let app;

app = new Vue({
    el: '#app',
    data: {
        scrollY: 0,
        navOpacity: 0,
        isDrawerOpen: false,
        mounted: false,
        isDarkMode: false
    },
    methods: {
            sgn(t, x) {
                let k = 1. / (1. - 2 * t);
                if (x <= t) return 0;
                else if (x >= 1 - t) return 1;
                else {
                    return k * (x - t);
                }
            },
            handleScroll() {
                this.scrollY = window.scrollY;
                this.navOpacity = this.sgn(.0, Math.min(1, Math.max(0, window.scrollY / (this.pageHeadHeight() - this.navBarHeight() * 0.8))));
                const {navBar, navBackground, navTitle, extraContainer, streamContainer} = this.$refs;

                if (this.navOpacity >= 1) {
                    navBackground.style.opacity = 1;
                    navTitle.style.opacity = 1;
                } else {
                    navBackground.style.opacity = 0;
                    navTitle.style.opacity = 0;
                }
            },
            handleResize() {
                const {navBar, navBackground, navTitle, extraContainer, streamContainer} = this.$refs;
                extraContainer.style.left = (streamContainer.offsetWidth - extraContainer.offsetWidth) + 'px';
            },
            navBarHeight() {
                return this.$refs.navBar.offsetHeight;
            },
            pageHeadHeight() {
                return this.$refs.pageHead.offsetHeight;
            },
            toggleDrawer() {
                this.isDrawerOpen = !this.isDrawerOpen;
                document.getElementsByTagName('html')[0].style.overflow = this.isDrawerOpen ? 'hidden' : 'unset';
            },
            closeDrawer() {
                this.isDrawerOpen = false;
                document.getElementsByTagName('html')[0].style.overflow = this.isDrawerOpen ? 'hidden' : 'unset';
            },
            toggleDarkMode() {
                this.isDarkMode = !this.isDarkMode;
                if (this.isDarkMode==true){
                    document.cookie = "night=1;path=/";
                    document.body.classList.add("night");
                } else {
                    document.cookie = "night=0;path=/";
                    document.body.classList.remove("night");
                }
            }
    },
    created() {
        window.addEventListener('scroll', this.handleScroll);
        window.addEventListener('resize', this.handleResize);
        window._nonDesktop = function () {
            let check = false;
            (function (a) {
                if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true;
            })(navigator.userAgent || navigator.vendor || window.opera);
            return check;
        };
        
        var night = document.cookie.replace(/(?:(?:^|.*;\s*)night\s*\=\s*([^;]*).*$)|^.*$/, "$1");
        if (night==""){
            if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
                
            }
        }else{
            
            if (night=="1") {
                this.toggleDarkMode();
            }
        }
    },
    mounted() {
        this.handleScroll();
        this.handleResize();
        this.mounted = true;

        
    },
    destroyed() {
        window.removeEventListener('scroll', this.handleScroll);
        window.removeEventListener('resize', this.handleResize);
    }
});
</script>

<script src="https://wondrouswisdomcard.gitee.io/js/journal.js"></script>
    </body>
</html>
